Home | History | Annotate | Download | only in launch
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
      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.ide.eclipse.adt.internal.launch;
     18 
     19 import com.android.ddmlib.AndroidDebugBridge;
     20 import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
     21 import com.android.ddmlib.Client;
     22 import com.android.ddmlib.IDevice;
     23 import com.android.ddmlib.IDevice.DeviceState;
     24 import com.android.ddmuilib.ImageLoader;
     25 import com.android.ddmuilib.TableHelper;
     26 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
     27 import com.android.ide.eclipse.adt.internal.sdk.AdtConsoleSdkLog;
     28 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     29 import com.android.ide.eclipse.ddms.DdmsPlugin;
     30 import com.android.sdklib.AndroidVersion;
     31 import com.android.sdklib.IAndroidTarget;
     32 import com.android.sdklib.internal.avd.AvdInfo;
     33 import com.android.sdkuilib.internal.widgets.AvdSelector;
     34 import com.android.sdkuilib.internal.widgets.AvdSelector.DisplayMode;
     35 import com.android.sdkuilib.internal.widgets.AvdSelector.IAvdFilter;
     36 
     37 import org.eclipse.jface.dialogs.Dialog;
     38 import org.eclipse.jface.dialogs.IDialogConstants;
     39 import org.eclipse.jface.viewers.ILabelProviderListener;
     40 import org.eclipse.jface.viewers.IStructuredContentProvider;
     41 import org.eclipse.jface.viewers.ITableLabelProvider;
     42 import org.eclipse.jface.viewers.StructuredSelection;
     43 import org.eclipse.jface.viewers.TableViewer;
     44 import org.eclipse.jface.viewers.Viewer;
     45 import org.eclipse.swt.SWT;
     46 import org.eclipse.swt.SWTException;
     47 import org.eclipse.swt.events.SelectionAdapter;
     48 import org.eclipse.swt.events.SelectionEvent;
     49 import org.eclipse.swt.graphics.Image;
     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.Control;
     55 import org.eclipse.swt.widgets.Display;
     56 import org.eclipse.swt.widgets.Label;
     57 import org.eclipse.swt.widgets.Shell;
     58 import org.eclipse.swt.widgets.Table;
     59 
     60 import java.util.ArrayList;
     61 import java.util.List;
     62 
     63 /**
     64  * A dialog that lets the user choose a device to deploy an application.
     65  * The user can either choose an exiting running device (including running emulators)
     66  * or start a new emulator using an Android Virtual Device configuration that matches
     67  * the current project.
     68  */
     69 public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener {
     70 
     71     private final static int ICON_WIDTH = 16;
     72 
     73     private Table mDeviceTable;
     74     private TableViewer mViewer;
     75     private AvdSelector mPreferredAvdSelector;
     76 
     77     private Image mDeviceImage;
     78     private Image mEmulatorImage;
     79     private Image mMatchImage;
     80     private Image mNoMatchImage;
     81     private Image mWarningImage;
     82 
     83     private final DeviceChooserResponse mResponse;
     84     private final String mPackageName;
     85     private final IAndroidTarget mProjectTarget;
     86     private final AndroidVersion mMinApiVersion;
     87     private final Sdk mSdk;
     88 
     89     private Button mDeviceRadioButton;
     90     private Button mUseDeviceForFutureLaunchesCheckbox;
     91     private boolean mUseDeviceForFutureLaunches;
     92 
     93     private boolean mDisableAvdSelectionChange = false;
     94 
     95     /**
     96      * Basic Content Provider for a table full of {@link IDevice} objects. The input is
     97      * a {@link AndroidDebugBridge}.
     98      */
     99     private class ContentProvider implements IStructuredContentProvider {
    100         @Override
    101         public Object[] getElements(Object inputElement) {
    102             if (inputElement instanceof AndroidDebugBridge) {
    103                 return findCompatibleDevices(((AndroidDebugBridge)inputElement).getDevices());
    104             }
    105 
    106             return new Object[0];
    107         }
    108 
    109         private Object[] findCompatibleDevices(IDevice[] devices) {
    110             if (devices == null) {
    111                 return null;
    112             }
    113 
    114             List<IDevice> compatibleDevices = new ArrayList<IDevice>(devices.length);
    115             for (IDevice device : devices) {
    116                 AndroidVersion deviceVersion = Sdk.getDeviceVersion(device);
    117                 if (deviceVersion == null || deviceVersion.canRun(mMinApiVersion)) {
    118                     compatibleDevices.add(device);
    119                 }
    120             }
    121 
    122             return compatibleDevices.toArray();
    123         }
    124 
    125         @Override
    126         public void dispose() {
    127             // pass
    128         }
    129 
    130         @Override
    131         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
    132             // pass
    133         }
    134     }
    135 
    136     /**
    137      * A Label Provider for the {@link TableViewer} in {@link DeviceChooserDialog}.
    138      * It provides labels and images for {@link IDevice} objects.
    139      */
    140     private class LabelProvider implements ITableLabelProvider {
    141 
    142         @Override
    143         public Image getColumnImage(Object element, int columnIndex) {
    144             if (element instanceof IDevice) {
    145                 IDevice device = (IDevice)element;
    146                 switch (columnIndex) {
    147                     case 0:
    148                         return device.isEmulator() ? mEmulatorImage : mDeviceImage;
    149 
    150                     case 2:
    151                         // check for compatibility.
    152                         if (device.isEmulator() == false) { // physical device
    153                             // get the version of the device
    154                             AndroidVersion deviceVersion = Sdk.getDeviceVersion(device);
    155                             if (deviceVersion == null) {
    156                                 return mWarningImage;
    157                             } else {
    158                                 if (!deviceVersion.canRun(mMinApiVersion)) {
    159                                     return mNoMatchImage;
    160                                 }
    161 
    162                                 // if the project is compiling against an add-on,
    163                                 // the optional API may be missing from the device.
    164                                 return mProjectTarget.isPlatform() ?
    165                                         mMatchImage : mWarningImage;
    166                             }
    167                         } else {
    168                             // get the AvdInfo
    169                             AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName(),
    170                                     true /*validAvdOnly*/);
    171                             AvdCompatibility.Compatibility c =
    172                                     AvdCompatibility.canRun(info, mProjectTarget,
    173                                             mMinApiVersion);
    174                             switch (c) {
    175                                 case YES:
    176                                     return mMatchImage;
    177                                 case NO:
    178                                     return mNoMatchImage;
    179                                 case UNKNOWN:
    180                                     return mWarningImage;
    181                             }
    182                         }
    183                 }
    184             }
    185 
    186             return null;
    187         }
    188 
    189         @Override
    190         public String getColumnText(Object element, int columnIndex) {
    191             if (element instanceof IDevice) {
    192                 IDevice device = (IDevice)element;
    193                 switch (columnIndex) {
    194                     case 0:
    195                         return device.getName();
    196                     case 1:
    197                         if (device.isEmulator()) {
    198                             return device.getAvdName();
    199                         } else {
    200                             return "N/A"; // devices don't have AVD names.
    201                         }
    202                     case 2:
    203                         if (device.isEmulator()) {
    204                             AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName(),
    205                                     true /*validAvdOnly*/);
    206                             if (info == null) {
    207                                 return "?";
    208                             }
    209                             return info.getTarget().getFullName();
    210                         } else {
    211                             String deviceBuild = device.getProperty(IDevice.PROP_BUILD_VERSION);
    212                             if (deviceBuild == null) {
    213                                 return "unknown";
    214                             }
    215                             return deviceBuild;
    216                         }
    217                     case 3:
    218                         String debuggable = device.getProperty(IDevice.PROP_DEBUGGABLE);
    219                         if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$
    220                             return "Yes";
    221                         } else {
    222                             return "";
    223                         }
    224                     case 4:
    225                         return getStateString(device);
    226                 }
    227             }
    228 
    229             return null;
    230         }
    231 
    232         @Override
    233         public void addListener(ILabelProviderListener listener) {
    234             // pass
    235         }
    236 
    237         @Override
    238         public void dispose() {
    239             // pass
    240         }
    241 
    242         @Override
    243         public boolean isLabelProperty(Object element, String property) {
    244             // pass
    245             return false;
    246         }
    247 
    248         @Override
    249         public void removeListener(ILabelProviderListener listener) {
    250             // pass
    251         }
    252     }
    253 
    254     public static class DeviceChooserResponse {
    255         private AvdInfo mAvdToLaunch;
    256         private IDevice mDeviceToUse;
    257         private boolean mUseDeviceForFutureLaunches;
    258 
    259         public void setDeviceToUse(IDevice d) {
    260             mDeviceToUse = d;
    261             mAvdToLaunch = null;
    262         }
    263 
    264         public void setAvdToLaunch(AvdInfo avd) {
    265             mAvdToLaunch = avd;
    266             mDeviceToUse = null;
    267         }
    268 
    269         public IDevice getDeviceToUse() {
    270             return mDeviceToUse;
    271         }
    272 
    273         public AvdInfo getAvdToLaunch() {
    274             return mAvdToLaunch;
    275         }
    276 
    277         public void setUseDeviceForFutureLaunches(boolean en) {
    278             mUseDeviceForFutureLaunches = en;
    279         }
    280 
    281         public boolean useDeviceForFutureLaunches() {
    282             return mUseDeviceForFutureLaunches;
    283         }
    284     }
    285 
    286     public DeviceChooserDialog(Shell parent, DeviceChooserResponse response, String packageName,
    287             IAndroidTarget projectTarget, AndroidVersion minApiVersion,
    288             boolean useDeviceForFutureLaunches) {
    289         super(parent);
    290 
    291         mResponse = response;
    292         mPackageName = packageName;
    293         mProjectTarget = projectTarget;
    294         mMinApiVersion = minApiVersion;
    295         mSdk = Sdk.getCurrent();
    296         mUseDeviceForFutureLaunches = useDeviceForFutureLaunches;
    297 
    298         AndroidDebugBridge.addDeviceChangeListener(this);
    299         loadImages();
    300     }
    301 
    302     private void cleanup() {
    303         // done listening.
    304         AndroidDebugBridge.removeDeviceChangeListener(this);
    305     }
    306 
    307     @Override
    308     protected void okPressed() {
    309         cleanup();
    310         super.okPressed();
    311     }
    312 
    313     @Override
    314     protected void cancelPressed() {
    315         cleanup();
    316         super.cancelPressed();
    317     }
    318 
    319     @Override
    320     protected Control createContents(Composite parent) {
    321         Control content = super.createContents(parent);
    322 
    323         // this must be called after createContents() has happened so that the
    324         // ok button has been created (it's created after the call to createDialogArea)
    325         updateDefaultSelection();
    326 
    327         return content;
    328     }
    329 
    330     /**
    331      * Create the button bar: We override the Dialog implementation of this method
    332      * so that we can create the checkbox at the same level as the 'Cancel' and 'OK' buttons.
    333      */
    334     @Override
    335     protected Control createButtonBar(Composite parent) {
    336         Composite composite = new Composite(parent, SWT.NONE);
    337 
    338         GridLayout layout = new GridLayout(1, false);
    339         layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
    340         composite.setLayout(layout);
    341         composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    342 
    343         mUseDeviceForFutureLaunchesCheckbox = new Button(composite, SWT.CHECK);
    344         mUseDeviceForFutureLaunchesCheckbox.setSelection(mUseDeviceForFutureLaunches);
    345         mResponse.setUseDeviceForFutureLaunches(mUseDeviceForFutureLaunches);
    346         mUseDeviceForFutureLaunchesCheckbox.setText("Use same device for future launches");
    347         mUseDeviceForFutureLaunchesCheckbox.addSelectionListener(new SelectionAdapter() {
    348             @Override
    349             public void widgetSelected(SelectionEvent e) {
    350                 mUseDeviceForFutureLaunches =
    351                         mUseDeviceForFutureLaunchesCheckbox.getSelection();
    352                 mResponse.setUseDeviceForFutureLaunches(mUseDeviceForFutureLaunches);
    353             }
    354         });
    355         mUseDeviceForFutureLaunchesCheckbox.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    356 
    357         createButton(composite, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
    358         createButton(composite, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
    359 
    360         return composite;
    361     }
    362 
    363     @Override
    364     protected Control createDialogArea(Composite parent) {
    365         // set dialog title
    366         getShell().setText("Android Device Chooser");
    367 
    368         Composite top = new Composite(parent, SWT.NONE);
    369         top.setLayout(new GridLayout(1, true));
    370 
    371         String msg;
    372         if (mProjectTarget.isPlatform()) {
    373             msg = String.format("Select a device with min API level %s.",
    374                     mMinApiVersion.getApiString());
    375         } else {
    376             msg = String.format("Select a device compatible with target %s.",
    377                     mProjectTarget.getFullName());
    378         }
    379         Label label = new Label(top, SWT.NONE);
    380         label.setText(msg);
    381 
    382         mDeviceRadioButton = new Button(top, SWT.RADIO);
    383         mDeviceRadioButton.setText("Choose a running Android device");
    384         mDeviceRadioButton.addSelectionListener(new SelectionAdapter() {
    385             @Override
    386             public void widgetSelected(SelectionEvent e) {
    387                 boolean deviceMode = mDeviceRadioButton.getSelection();
    388 
    389                 mDeviceTable.setEnabled(deviceMode);
    390                 mPreferredAvdSelector.setEnabled(!deviceMode);
    391 
    392                 if (deviceMode) {
    393                     handleDeviceSelection();
    394                 } else {
    395                     mResponse.setAvdToLaunch(mPreferredAvdSelector.getSelected());
    396                 }
    397 
    398                 enableOkButton();
    399             }
    400         });
    401         mDeviceRadioButton.setSelection(true);
    402 
    403 
    404         // offset the selector from the radio button
    405         Composite offsetComp = new Composite(top, SWT.NONE);
    406         offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    407         GridLayout layout = new GridLayout(1, false);
    408         layout.marginRight = layout.marginHeight = 0;
    409         layout.marginLeft = 30;
    410         offsetComp.setLayout(layout);
    411 
    412         mDeviceTable = new Table(offsetComp, SWT.SINGLE | SWT.FULL_SELECTION | SWT.BORDER);
    413         GridData gd;
    414         mDeviceTable.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
    415         gd.heightHint = 100;
    416 
    417         mDeviceTable.setHeaderVisible(true);
    418         mDeviceTable.setLinesVisible(true);
    419 
    420         TableHelper.createTableColumn(mDeviceTable, "Serial Number",
    421                 SWT.LEFT, "AAA+AAAAAAAAAAAAAAAAAAA", //$NON-NLS-1$
    422                 null /* prefs name */, null /* prefs store */);
    423 
    424         TableHelper.createTableColumn(mDeviceTable, "AVD Name",
    425                 SWT.LEFT, "AAAAAAAAAAAAAAAAAAA", //$NON-NLS-1$
    426                 null /* prefs name */, null /* prefs store */);
    427 
    428         TableHelper.createTableColumn(mDeviceTable, "Target",
    429                 SWT.LEFT, "AAA+Android 9.9.9", //$NON-NLS-1$
    430                 null /* prefs name */, null /* prefs store */);
    431 
    432         TableHelper.createTableColumn(mDeviceTable, "Debug",
    433                 SWT.LEFT, "Debug", //$NON-NLS-1$
    434                 null /* prefs name */, null /* prefs store */);
    435 
    436         TableHelper.createTableColumn(mDeviceTable, "State",
    437                 SWT.LEFT, "bootloader", //$NON-NLS-1$
    438                 null /* prefs name */, null /* prefs store */);
    439 
    440         // create the viewer for it
    441         mViewer = new TableViewer(mDeviceTable);
    442         mViewer.setContentProvider(new ContentProvider());
    443         mViewer.setLabelProvider(new LabelProvider());
    444         mViewer.setInput(AndroidDebugBridge.getBridge());
    445 
    446         mDeviceTable.addSelectionListener(new SelectionAdapter() {
    447             /**
    448              * Handles single-click selection on the device selector.
    449              * {@inheritDoc}
    450              */
    451             @Override
    452             public void widgetSelected(SelectionEvent e) {
    453                 handleDeviceSelection();
    454             }
    455 
    456             /**
    457              * Handles double-click selection on the device selector.
    458              * Note that the single-click handler will probably already have been called.
    459              * {@inheritDoc}
    460              */
    461             @Override
    462             public void widgetDefaultSelected(SelectionEvent e) {
    463                 handleDeviceSelection();
    464                 if (isOkButtonEnabled()) {
    465                     okPressed();
    466                 }
    467             }
    468         });
    469 
    470         Button radio2 = new Button(top, SWT.RADIO);
    471         radio2.setText("Launch a new Android Virtual Device");
    472 
    473         // offset the selector from the radio button
    474         offsetComp = new Composite(top, SWT.NONE);
    475         offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    476         layout = new GridLayout(1, false);
    477         layout.marginRight = layout.marginHeight = 0;
    478         layout.marginLeft = 30;
    479         offsetComp.setLayout(layout);
    480 
    481         mPreferredAvdSelector = new AvdSelector(offsetComp,
    482                 mSdk.getSdkOsLocation(),
    483                 mSdk.getAvdManager(),
    484                 new NonRunningAvdFilter(),
    485                 DisplayMode.SIMPLE_SELECTION,
    486                 new AdtConsoleSdkLog());
    487         mPreferredAvdSelector.setTableHeightHint(100);
    488         mPreferredAvdSelector.setEnabled(false);
    489         mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() {
    490             /**
    491              * Handles single-click selection on the AVD selector.
    492              * {@inheritDoc}
    493              */
    494             @Override
    495             public void widgetSelected(SelectionEvent e) {
    496                 if (mDisableAvdSelectionChange == false) {
    497                     mResponse.setAvdToLaunch(mPreferredAvdSelector.getSelected());
    498                     enableOkButton();
    499                 }
    500             }
    501 
    502             /**
    503              * Handles double-click selection on the AVD selector.
    504              *
    505              * Note that the single-click handler will probably already have been called
    506              * but the selected item can have changed in between.
    507              *
    508              * {@inheritDoc}
    509              */
    510             @Override
    511             public void widgetDefaultSelected(SelectionEvent e) {
    512                 widgetSelected(e);
    513                 if (isOkButtonEnabled()) {
    514                     okPressed();
    515                 }
    516             }
    517         });
    518 
    519         return top;
    520     }
    521 
    522     private void loadImages() {
    523         ImageLoader ddmUiLibLoader = ImageLoader.getDdmUiLibLoader();
    524         Display display = DdmsPlugin.getDisplay();
    525         IconFactory factory = IconFactory.getInstance();
    526 
    527         if (mDeviceImage == null) {
    528             mDeviceImage = ddmUiLibLoader.loadImage(display,
    529                     "device.png", //$NON-NLS-1$
    530                     ICON_WIDTH, ICON_WIDTH,
    531                     display.getSystemColor(SWT.COLOR_RED));
    532         }
    533         if (mEmulatorImage == null) {
    534             mEmulatorImage = ddmUiLibLoader.loadImage(display,
    535                     "emulator.png", ICON_WIDTH, ICON_WIDTH, //$NON-NLS-1$
    536                     display.getSystemColor(SWT.COLOR_BLUE));
    537         }
    538 
    539         if (mMatchImage == null) {
    540             mMatchImage = factory.getIcon("match", //$NON-NLS-1$
    541                     IconFactory.COLOR_GREEN,
    542                     IconFactory.SHAPE_DEFAULT);
    543         }
    544 
    545         if (mNoMatchImage == null) {
    546             mNoMatchImage = factory.getIcon("error", //$NON-NLS-1$
    547                     IconFactory.COLOR_RED,
    548                     IconFactory.SHAPE_DEFAULT);
    549         }
    550 
    551         if (mWarningImage == null) {
    552             mWarningImage = factory.getIcon("warning", //$NON-NLS-1$
    553                     SWT.COLOR_YELLOW,
    554                     IconFactory.SHAPE_DEFAULT);
    555         }
    556 
    557     }
    558 
    559     /**
    560      * Returns a display string representing the state of the device.
    561      * @param d the device
    562      */
    563     private static String getStateString(IDevice d) {
    564         DeviceState deviceState = d.getState();
    565         if (deviceState == DeviceState.ONLINE) {
    566             return "Online";
    567         } else if (deviceState == DeviceState.OFFLINE) {
    568             return "Offline";
    569         } else if (deviceState == DeviceState.BOOTLOADER) {
    570             return "Bootloader";
    571         }
    572 
    573         return "??";
    574     }
    575 
    576     /**
    577      * Sent when the a device is connected to the {@link AndroidDebugBridge}.
    578      * <p/>
    579      * This is sent from a non UI thread.
    580      * @param device the new device.
    581      *
    582      * @see IDeviceChangeListener#deviceConnected(IDevice)
    583      */
    584     @Override
    585     public void deviceConnected(IDevice device) {
    586         final DeviceChooserDialog dialog = this;
    587         exec(new Runnable() {
    588             @Override
    589             public void run() {
    590                 if (mDeviceTable.isDisposed() == false) {
    591                     // refresh all
    592                     mViewer.refresh();
    593 
    594                     // update the selection
    595                     updateDefaultSelection();
    596 
    597                     // update the display of AvdInfo (since it's filtered to only display
    598                     // non running AVD.)
    599                     refillAvdList(false /*reloadAvds*/);
    600                 } else {
    601                     // table is disposed, we need to do something.
    602                     // lets remove ourselves from the listener.
    603                     AndroidDebugBridge.removeDeviceChangeListener(dialog);
    604                 }
    605 
    606             }
    607         });
    608     }
    609 
    610     /**
    611      * Sent when the a device is connected to the {@link AndroidDebugBridge}.
    612      * <p/>
    613      * This is sent from a non UI thread.
    614      * @param device the new device.
    615      *
    616      * @see IDeviceChangeListener#deviceDisconnected(IDevice)
    617      */
    618     @Override
    619     public void deviceDisconnected(IDevice device) {
    620         deviceConnected(device);
    621     }
    622 
    623     /**
    624      * Sent when a device data changed, or when clients are started/terminated on the device.
    625      * <p/>
    626      * This is sent from a non UI thread.
    627      * @param device the device that was updated.
    628      * @param changeMask the mask indicating what changed.
    629      *
    630      * @see IDeviceChangeListener#deviceChanged(IDevice, int)
    631      */
    632     @Override
    633     public void deviceChanged(final IDevice device, int changeMask) {
    634         if ((changeMask & (IDevice.CHANGE_STATE | IDevice.CHANGE_BUILD_INFO)) != 0) {
    635             final DeviceChooserDialog dialog = this;
    636             exec(new Runnable() {
    637                 @Override
    638                 public void run() {
    639                     if (mDeviceTable.isDisposed() == false) {
    640                         // refresh the device
    641                         mViewer.refresh(device);
    642 
    643                         // update the defaultSelection.
    644                         updateDefaultSelection();
    645 
    646                         // update the display of AvdInfo (since it's filtered to only display
    647                         // non running AVD). This is done on deviceChanged because the avd name
    648                         // of a (emulator) device may be updated as the emulator boots.
    649 
    650                         refillAvdList(false /*reloadAvds*/);
    651 
    652                         // if the changed device is the current selection,
    653                         // we update the OK button based on its state.
    654                         if (device == mResponse.getDeviceToUse()) {
    655                             enableOkButton();
    656                         }
    657 
    658                     } else {
    659                         // table is disposed, we need to do something.
    660                         // lets remove ourselves from the listener.
    661                         AndroidDebugBridge.removeDeviceChangeListener(dialog);
    662                     }
    663                 }
    664             });
    665         }
    666     }
    667 
    668     /**
    669      * Returns whether the dialog is in "device" mode (true), or in "avd" mode (false).
    670      */
    671     private boolean isDeviceMode() {
    672         return mDeviceRadioButton.getSelection();
    673     }
    674 
    675     /**
    676      * Enables or disables the OK button of the dialog based on various selections in the dialog.
    677      */
    678     private void enableOkButton() {
    679         Button okButton = getButton(IDialogConstants.OK_ID);
    680 
    681         if (isDeviceMode()) {
    682             okButton.setEnabled(mResponse.getDeviceToUse() != null &&
    683                     mResponse.getDeviceToUse().isOnline());
    684         } else {
    685             okButton.setEnabled(mResponse.getAvdToLaunch() != null);
    686         }
    687     }
    688 
    689     /**
    690      * Returns true if the ok button is enabled.
    691      */
    692     private boolean isOkButtonEnabled() {
    693         Button okButton = getButton(IDialogConstants.OK_ID);
    694         return okButton.isEnabled();
    695     }
    696 
    697     /**
    698      * Executes the {@link Runnable} in the UI thread.
    699      * @param runnable the runnable to execute.
    700      */
    701     private void exec(Runnable runnable) {
    702         try {
    703             Display display = mDeviceTable.getDisplay();
    704             display.asyncExec(runnable);
    705         } catch (SWTException e) {
    706             // tree is disposed, we need to do something. lets remove ourselves from the listener.
    707             AndroidDebugBridge.removeDeviceChangeListener(this);
    708         }
    709     }
    710 
    711     private void handleDeviceSelection() {
    712         int count = mDeviceTable.getSelectionCount();
    713         if (count != 1) {
    714             handleSelection(null);
    715         } else {
    716             int index = mDeviceTable.getSelectionIndex();
    717             Object data = mViewer.getElementAt(index);
    718             if (data instanceof IDevice) {
    719                 handleSelection((IDevice)data);
    720             } else {
    721                 handleSelection(null);
    722             }
    723         }
    724     }
    725 
    726     private void handleSelection(IDevice device) {
    727         mResponse.setDeviceToUse(device);
    728         enableOkButton();
    729     }
    730 
    731     /**
    732      * Look for a default device to select. This is done by looking for the running
    733      * clients on each device and finding one similar to the one being launched.
    734      * <p/>
    735      * This is done every time the device list changed unless there is a already selection.
    736      */
    737     private void updateDefaultSelection() {
    738         if (mDeviceTable.getSelectionCount() == 0) {
    739             AndroidDebugBridge bridge = AndroidDebugBridge.getBridge();
    740 
    741             IDevice[] devices = bridge.getDevices();
    742 
    743             for (IDevice device : devices) {
    744                 Client[] clients = device.getClients();
    745 
    746                 for (Client client : clients) {
    747 
    748                     if (mPackageName.equals(client.getClientData().getClientDescription())) {
    749                         // found a match! Select it.
    750                         mViewer.setSelection(new StructuredSelection(device));
    751                         handleSelection(device);
    752 
    753                         // and we're done.
    754                         return;
    755                     }
    756                 }
    757             }
    758         }
    759 
    760         handleDeviceSelection();
    761     }
    762 
    763     private final class NonRunningAvdFilter implements IAvdFilter {
    764 
    765         private IDevice[] mDevices;
    766 
    767         @Override
    768         public void prepare() {
    769             mDevices = AndroidDebugBridge.getBridge().getDevices();
    770         }
    771 
    772         @Override
    773         public boolean accept(AvdInfo avd) {
    774             if (mDevices != null) {
    775                 for (IDevice d : mDevices) {
    776                     // do not accept running avd's
    777                     if (avd.getName().equals(d.getAvdName())) {
    778                         return false;
    779                     }
    780 
    781                     // only accept avd's that can actually run the project
    782                     AvdCompatibility.Compatibility c =
    783                             AvdCompatibility.canRun(avd, mProjectTarget, mMinApiVersion);
    784                     return (c == AvdCompatibility.Compatibility.NO) ? false : true;
    785                 }
    786             }
    787 
    788             return true;
    789         }
    790 
    791         @Override
    792         public void cleanup() {
    793             mDevices = null;
    794         }
    795     }
    796 
    797     /**
    798      * Refills the AVD list keeping the current selection.
    799      */
    800     private void refillAvdList(boolean reloadAvds) {
    801         // save the current selection
    802         AvdInfo selected = mPreferredAvdSelector.getSelected();
    803 
    804         // disable selection change.
    805         mDisableAvdSelectionChange = true;
    806 
    807         // refresh the list
    808         mPreferredAvdSelector.refresh(false);
    809 
    810         // attempt to reselect the proper avd if needed
    811         if (selected != null) {
    812             if (mPreferredAvdSelector.setSelection(selected) == false) {
    813                 // looks like the selection is lost. this can happen if an emulator
    814                 // running the AVD that was selected was launched from outside of Eclipse).
    815                 mResponse.setAvdToLaunch(null);
    816                 enableOkButton();
    817             }
    818         }
    819 
    820         // enable the selection change
    821         mDisableAvdSelectionChange = false;
    822     }
    823 }
    824 
    825