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
getElements(Object inputElement)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 
findCompatibleDevices(IDevice[] devices)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
dispose()126         public void dispose() {
127             // pass
128         }
129 
130         @Override
inputChanged(Viewer viewer, Object oldInput, Object newInput)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
getColumnImage(Object element, int columnIndex)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
getColumnText(Object element, int columnIndex)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
addListener(ILabelProviderListener listener)233         public void addListener(ILabelProviderListener listener) {
234             // pass
235         }
236 
237         @Override
dispose()238         public void dispose() {
239             // pass
240         }
241 
242         @Override
isLabelProperty(Object element, String property)243         public boolean isLabelProperty(Object element, String property) {
244             // pass
245             return false;
246         }
247 
248         @Override
removeListener(ILabelProviderListener listener)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 
setDeviceToUse(IDevice d)259         public void setDeviceToUse(IDevice d) {
260             mDeviceToUse = d;
261             mAvdToLaunch = null;
262         }
263 
setAvdToLaunch(AvdInfo avd)264         public void setAvdToLaunch(AvdInfo avd) {
265             mAvdToLaunch = avd;
266             mDeviceToUse = null;
267         }
268 
getDeviceToUse()269         public IDevice getDeviceToUse() {
270             return mDeviceToUse;
271         }
272 
getAvdToLaunch()273         public AvdInfo getAvdToLaunch() {
274             return mAvdToLaunch;
275         }
276 
setUseDeviceForFutureLaunches(boolean en)277         public void setUseDeviceForFutureLaunches(boolean en) {
278             mUseDeviceForFutureLaunches = en;
279         }
280 
useDeviceForFutureLaunches()281         public boolean useDeviceForFutureLaunches() {
282             return mUseDeviceForFutureLaunches;
283         }
284     }
285 
DeviceChooserDialog(Shell parent, DeviceChooserResponse response, String packageName, IAndroidTarget projectTarget, AndroidVersion minApiVersion, boolean useDeviceForFutureLaunches)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 
cleanup()302     private void cleanup() {
303         // done listening.
304         AndroidDebugBridge.removeDeviceChangeListener(this);
305     }
306 
307     @Override
okPressed()308     protected void okPressed() {
309         cleanup();
310         super.okPressed();
311     }
312 
313     @Override
cancelPressed()314     protected void cancelPressed() {
315         cleanup();
316         super.cancelPressed();
317     }
318 
319     @Override
createContents(Composite parent)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
createButtonBar(Composite parent)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
createDialogArea(Composite parent)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 
loadImages()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      */
getStateString(IDevice d)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
deviceConnected(IDevice device)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
deviceDisconnected(IDevice device)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
deviceChanged(final IDevice device, int changeMask)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      */
isDeviceMode()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      */
enableOkButton()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      */
isOkButtonEnabled()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      */
exec(Runnable runnable)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 
handleDeviceSelection()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 
handleSelection(IDevice device)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      */
updateDefaultSelection()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
prepare()768         public void prepare() {
769             mDevices = AndroidDebugBridge.getBridge().getDevices();
770         }
771 
772         @Override
accept(AvdInfo avd)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
cleanup()792         public void cleanup() {
793             mDevices = null;
794         }
795     }
796 
797     /**
798      * Refills the AVD list keeping the current selection.
799      */
refillAvdList(boolean reloadAvds)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