• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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.ddmuilib;
18 
19 import com.android.ddmlib.AndroidDebugBridge;
20 import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
21 import com.android.ddmlib.AndroidDebugBridge.IDebugBridgeChangeListener;
22 import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
23 import com.android.ddmlib.Client;
24 import com.android.ddmlib.ClientData;
25 import com.android.ddmlib.ClientData.DebuggerStatus;
26 import com.android.ddmlib.DdmPreferences;
27 import com.android.ddmlib.IDevice;
28 import com.android.ddmlib.IDevice.DeviceState;
29 
30 import org.eclipse.jface.preference.IPreferenceStore;
31 import org.eclipse.jface.viewers.ILabelProviderListener;
32 import org.eclipse.jface.viewers.ITableLabelProvider;
33 import org.eclipse.jface.viewers.ITreeContentProvider;
34 import org.eclipse.jface.viewers.TreePath;
35 import org.eclipse.jface.viewers.TreeSelection;
36 import org.eclipse.jface.viewers.TreeViewer;
37 import org.eclipse.jface.viewers.Viewer;
38 import org.eclipse.swt.SWT;
39 import org.eclipse.swt.SWTException;
40 import org.eclipse.swt.events.SelectionAdapter;
41 import org.eclipse.swt.events.SelectionEvent;
42 import org.eclipse.swt.graphics.Image;
43 import org.eclipse.swt.layout.FillLayout;
44 import org.eclipse.swt.widgets.Composite;
45 import org.eclipse.swt.widgets.Control;
46 import org.eclipse.swt.widgets.Display;
47 import org.eclipse.swt.widgets.Tree;
48 import org.eclipse.swt.widgets.TreeColumn;
49 import org.eclipse.swt.widgets.TreeItem;
50 
51 import java.util.ArrayList;
52 
53 /**
54  * A display of both the devices and their clients.
55  */
56 public final class DevicePanel extends Panel implements IDebugBridgeChangeListener,
57         IDeviceChangeListener, IClientChangeListener {
58 
59     private final static String PREFS_COL_NAME_SERIAL = "devicePanel.Col0"; //$NON-NLS-1$
60     private final static String PREFS_COL_PID_STATE = "devicePanel.Col1"; //$NON-NLS-1$
61     private final static String PREFS_COL_PORT_BUILD = "devicePanel.Col4"; //$NON-NLS-1$
62 
63     private final static int DEVICE_COL_SERIAL = 0;
64     private final static int DEVICE_COL_STATE = 1;
65     // col 2, 3 not used.
66     private final static int DEVICE_COL_BUILD = 4;
67 
68     private final static int CLIENT_COL_NAME = 0;
69     private final static int CLIENT_COL_PID = 1;
70     private final static int CLIENT_COL_THREAD = 2;
71     private final static int CLIENT_COL_HEAP = 3;
72     private final static int CLIENT_COL_PORT = 4;
73 
74     public final static int ICON_WIDTH = 16;
75     public final static String ICON_THREAD = "thread.png"; //$NON-NLS-1$
76     public final static String ICON_HEAP = "heap.png"; //$NON-NLS-1$
77     public final static String ICON_HALT = "halt.png"; //$NON-NLS-1$
78     public final static String ICON_GC = "gc.png"; //$NON-NLS-1$
79     public final static String ICON_HPROF = "hprof.png"; //$NON-NLS-1$
80     public final static String ICON_TRACING_START = "tracing_start.png"; //$NON-NLS-1$
81     public final static String ICON_TRACING_STOP = "tracing_stop.png"; //$NON-NLS-1$
82 
83     private IDevice mCurrentDevice;
84     private Client mCurrentClient;
85 
86     private Tree mTree;
87     private TreeViewer mTreeViewer;
88 
89     private Image mDeviceImage;
90     private Image mEmulatorImage;
91 
92     private Image mThreadImage;
93     private Image mHeapImage;
94     private Image mWaitingImage;
95     private Image mDebuggerImage;
96     private Image mDebugErrorImage;
97 
98     private final ArrayList<IUiSelectionListener> mListeners = new ArrayList<IUiSelectionListener>();
99 
100     private final ArrayList<IDevice> mDevicesToExpand = new ArrayList<IDevice>();
101 
102     private boolean mAdvancedPortSupport;
103 
104     /**
105      * A Content provider for the {@link TreeViewer}.
106      * <p/>
107      * The input is a {@link AndroidDebugBridge}. First level elements are {@link IDevice} objects,
108      * and second level elements are {@link Client} object.
109      */
110     private class ContentProvider implements ITreeContentProvider {
111         @Override
getChildren(Object parentElement)112         public Object[] getChildren(Object parentElement) {
113             if (parentElement instanceof IDevice) {
114                 return ((IDevice)parentElement).getClients();
115             }
116             return new Object[0];
117         }
118 
119         @Override
getParent(Object element)120         public Object getParent(Object element) {
121             if (element instanceof Client) {
122                 return ((Client)element).getDevice();
123             }
124             return null;
125         }
126 
127         @Override
hasChildren(Object element)128         public boolean hasChildren(Object element) {
129             if (element instanceof IDevice) {
130                 return ((IDevice)element).hasClients();
131             }
132 
133             // Clients never have children.
134             return false;
135         }
136 
137         @Override
getElements(Object inputElement)138         public Object[] getElements(Object inputElement) {
139             if (inputElement instanceof AndroidDebugBridge) {
140                 return ((AndroidDebugBridge)inputElement).getDevices();
141             }
142             return new Object[0];
143         }
144 
145         @Override
dispose()146         public void dispose() {
147             // pass
148         }
149 
150         @Override
inputChanged(Viewer viewer, Object oldInput, Object newInput)151         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
152             // pass
153         }
154     }
155 
156     /**
157      * A Label Provider for the {@link TreeViewer} in {@link DevicePanel}. It provides
158      * labels and images for {@link IDevice} and {@link Client} objects.
159      */
160     private class LabelProvider implements ITableLabelProvider {
161         private static final String DEVICE_MODEL_PROPERTY = "ro.product.model"; //$NON-NLS-1$
162         private static final String DEVICE_MANUFACTURER_PROPERTY = "ro.product.manufacturer"; //$NON-NLS-1$
163 
164         @Override
getColumnImage(Object element, int columnIndex)165         public Image getColumnImage(Object element, int columnIndex) {
166             if (columnIndex == DEVICE_COL_SERIAL && element instanceof IDevice) {
167                 IDevice device = (IDevice)element;
168                 if (device.isEmulator()) {
169                     return mEmulatorImage;
170                 }
171 
172                 return mDeviceImage;
173             } else if (element instanceof Client) {
174                 Client client = (Client)element;
175                 ClientData cd = client.getClientData();
176 
177                 switch (columnIndex) {
178                     case CLIENT_COL_NAME:
179                         switch (cd.getDebuggerConnectionStatus()) {
180                             case DEFAULT:
181                                 return null;
182                             case WAITING:
183                                 return mWaitingImage;
184                             case ATTACHED:
185                                 return mDebuggerImage;
186                             case ERROR:
187                                 return mDebugErrorImage;
188                         }
189                         return null;
190                     case CLIENT_COL_THREAD:
191                         if (client.isThreadUpdateEnabled()) {
192                             return mThreadImage;
193                         }
194                         return null;
195                     case CLIENT_COL_HEAP:
196                         if (client.isHeapUpdateEnabled()) {
197                             return mHeapImage;
198                         }
199                         return null;
200                 }
201             }
202             return null;
203         }
204 
205         @Override
getColumnText(Object element, int columnIndex)206         public String getColumnText(Object element, int columnIndex) {
207             if (element instanceof IDevice) {
208                 IDevice device = (IDevice)element;
209                 switch (columnIndex) {
210                     case DEVICE_COL_SERIAL:
211                         return getDeviceName(device);
212                     case DEVICE_COL_STATE:
213                         return getStateString(device);
214                     case DEVICE_COL_BUILD: {
215                         String version = device.getProperty(IDevice.PROP_BUILD_VERSION);
216                         if (version != null) {
217                             String debuggable = device.getProperty(IDevice.PROP_DEBUGGABLE);
218                             if (device.isEmulator()) {
219                                 String avdName = device.getAvdName();
220                                 if (avdName == null) {
221                                     avdName = "?"; // the device is probably not online yet, so
222                                                    // we don't know its AVD name just yet.
223                                 }
224                                 if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$
225                                     return String.format("%1$s [%2$s, debug]", avdName,
226                                             version);
227                                 } else {
228                                     return String.format("%1$s [%2$s]", avdName, version); //$NON-NLS-1$
229                                 }
230                             } else {
231                                 if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$
232                                     return String.format("%1$s, debug", version);
233                                 } else {
234                                     return String.format("%1$s", version); //$NON-NLS-1$
235                                 }
236                             }
237                         } else {
238                             return "unknown";
239                         }
240                     }
241                 }
242             } else if (element instanceof Client) {
243                 Client client = (Client)element;
244                 ClientData cd = client.getClientData();
245 
246                 switch (columnIndex) {
247                     case CLIENT_COL_NAME:
248                         String name = cd.getClientDescription();
249                         if (name != null) {
250                             return name;
251                         }
252                         return "?";
253                     case CLIENT_COL_PID:
254                         return Integer.toString(cd.getPid());
255                     case CLIENT_COL_PORT:
256                         if (mAdvancedPortSupport) {
257                             int port = client.getDebuggerListenPort();
258                             String portString = "?";
259                             if (port != 0) {
260                                 portString = Integer.toString(port);
261                             }
262                             if (client.isSelectedClient()) {
263                                 return String.format("%1$s / %2$d", portString, //$NON-NLS-1$
264                                         DdmPreferences.getSelectedDebugPort());
265                             }
266 
267                             return portString;
268                         }
269                 }
270             }
271             return null;
272         }
273 
getDeviceName(IDevice device)274         private String getDeviceName(IDevice device) {
275             StringBuilder sb = new StringBuilder(20);
276             sb.append(device.getSerialNumber());
277 
278             if (device.isEmulator()) {
279                 sb.append(String.format(" [%s]", device.getAvdName()));
280             } else {
281                 String manufacturer = device.getProperty(DEVICE_MANUFACTURER_PROPERTY);
282                 manufacturer = cleanupStringForDisplay(manufacturer);
283 
284                 String model = device.getProperty(DEVICE_MODEL_PROPERTY);
285                 model = cleanupStringForDisplay(model);
286 
287                 boolean hasManufacturer = manufacturer.length() > 0;
288                 boolean hasModel = model.length() > 0;
289                 if (hasManufacturer || hasModel) {
290                     sb.append(" [");                        //$NON-NLS-1$
291                     sb.append(manufacturer);
292 
293                     if (hasManufacturer && hasModel) {
294                         sb.append(':');
295                     }
296 
297                     sb.append(model);
298                     sb.append(']');
299                 }
300             }
301 
302             return sb.toString();
303         }
304 
cleanupStringForDisplay(String s)305         private String cleanupStringForDisplay(String s) {
306             if (s == null) {
307                 return "";
308             }
309 
310             StringBuilder sb = new StringBuilder(s.length());
311             for (int i = 0; i < s.length(); i++) {
312                 char c = s.charAt(i);
313 
314                 if (Character.isLetterOrDigit(c)) {
315                     sb.append(c);
316                 }
317             }
318 
319             return sb.toString();
320         }
321 
322         @Override
addListener(ILabelProviderListener listener)323         public void addListener(ILabelProviderListener listener) {
324             // pass
325         }
326 
327         @Override
dispose()328         public void dispose() {
329             // pass
330         }
331 
332         @Override
isLabelProperty(Object element, String property)333         public boolean isLabelProperty(Object element, String property) {
334             // pass
335             return false;
336         }
337 
338         @Override
removeListener(ILabelProviderListener listener)339         public void removeListener(ILabelProviderListener listener) {
340             // pass
341         }
342     }
343 
344     /**
345      * Classes which implement this interface provide methods that deals
346      * with {@link IDevice} and {@link Client} selection changes coming from the ui.
347      */
348     public interface IUiSelectionListener {
349         /**
350          * Sent when a new {@link IDevice} and {@link Client} are selected.
351          * @param selectedDevice the selected device. If null, no devices are selected.
352          * @param selectedClient The selected client. If null, no clients are selected.
353          */
selectionChanged(IDevice selectedDevice, Client selectedClient)354         public void selectionChanged(IDevice selectedDevice, Client selectedClient);
355     }
356 
357     /**
358      * Creates the {@link DevicePanel} object.
359      * @param loader
360      * @param advancedPortSupport if true the device panel will add support for selected client port
361      * and display the ports in the ui.
362      */
DevicePanel(boolean advancedPortSupport)363     public DevicePanel(boolean advancedPortSupport) {
364         mAdvancedPortSupport = advancedPortSupport;
365     }
366 
addSelectionListener(IUiSelectionListener listener)367     public void addSelectionListener(IUiSelectionListener listener) {
368         mListeners.add(listener);
369     }
370 
removeSelectionListener(IUiSelectionListener listener)371     public void removeSelectionListener(IUiSelectionListener listener) {
372         mListeners.remove(listener);
373     }
374 
375     @Override
createControl(Composite parent)376     protected Control createControl(Composite parent) {
377         loadImages(parent.getDisplay());
378 
379         parent.setLayout(new FillLayout());
380 
381         // create the tree and its column
382         mTree = new Tree(parent, SWT.SINGLE | SWT.FULL_SELECTION);
383         mTree.setHeaderVisible(true);
384         mTree.setLinesVisible(true);
385 
386         IPreferenceStore store = DdmUiPreferences.getStore();
387 
388         TableHelper.createTreeColumn(mTree, "Name", SWT.LEFT,
389                 "com.android.home", //$NON-NLS-1$
390                 PREFS_COL_NAME_SERIAL, store);
391         TableHelper.createTreeColumn(mTree, "", SWT.LEFT, //$NON-NLS-1$
392                 "Offline", //$NON-NLS-1$
393                 PREFS_COL_PID_STATE, store);
394 
395         TreeColumn col = new TreeColumn(mTree, SWT.NONE);
396         col.setWidth(ICON_WIDTH + 8);
397         col.setResizable(false);
398         col = new TreeColumn(mTree, SWT.NONE);
399         col.setWidth(ICON_WIDTH + 8);
400         col.setResizable(false);
401 
402         TableHelper.createTreeColumn(mTree, "", SWT.LEFT, //$NON-NLS-1$
403                 "9999-9999", //$NON-NLS-1$
404                 PREFS_COL_PORT_BUILD, store);
405 
406         // create the tree viewer
407         mTreeViewer = new TreeViewer(mTree);
408 
409         // make the device auto expanded.
410         mTreeViewer.setAutoExpandLevel(TreeViewer.ALL_LEVELS);
411 
412         // set up the content and label providers.
413         mTreeViewer.setContentProvider(new ContentProvider());
414         mTreeViewer.setLabelProvider(new LabelProvider());
415 
416         mTree.addSelectionListener(new SelectionAdapter() {
417             @Override
418             public void widgetSelected(SelectionEvent e) {
419                 notifyListeners();
420             }
421         });
422 
423         return mTree;
424     }
425 
426     /**
427      * Sets the focus to the proper control inside the panel.
428      */
429     @Override
setFocus()430     public void setFocus() {
431         mTree.setFocus();
432     }
433 
434     @Override
postCreation()435     protected void postCreation() {
436         // ask for notification of changes in AndroidDebugBridge (a new one is created when
437         // adb is restarted from a different location), IDevice and Client objects.
438         AndroidDebugBridge.addDebugBridgeChangeListener(this);
439         AndroidDebugBridge.addDeviceChangeListener(this);
440         AndroidDebugBridge.addClientChangeListener(this);
441     }
442 
dispose()443     public void dispose() {
444         AndroidDebugBridge.removeDebugBridgeChangeListener(this);
445         AndroidDebugBridge.removeDeviceChangeListener(this);
446         AndroidDebugBridge.removeClientChangeListener(this);
447     }
448 
449     /**
450      * Returns the selected {@link Client}. May be null.
451      */
getSelectedClient()452     public Client getSelectedClient() {
453         return mCurrentClient;
454     }
455 
456     /**
457      * Returns the selected {@link IDevice}. If a {@link Client} is selected, it returns the
458      * IDevice object containing the client.
459      */
getSelectedDevice()460     public IDevice getSelectedDevice() {
461         return mCurrentDevice;
462     }
463 
464     /**
465      * Kills the selected {@link Client} by sending its VM a halt command.
466      */
killSelectedClient()467     public void killSelectedClient() {
468         if (mCurrentClient != null) {
469             Client client = mCurrentClient;
470 
471             // reset the selection to the device.
472             TreePath treePath = new TreePath(new Object[] { mCurrentDevice });
473             TreeSelection treeSelection = new TreeSelection(treePath);
474             mTreeViewer.setSelection(treeSelection);
475 
476             client.kill();
477         }
478     }
479 
480     /**
481      * Forces a GC on the selected {@link Client}.
482      */
forceGcOnSelectedClient()483     public void forceGcOnSelectedClient() {
484         if (mCurrentClient != null) {
485             mCurrentClient.executeGarbageCollector();
486         }
487     }
488 
dumpHprof()489     public void dumpHprof() {
490         if (mCurrentClient != null) {
491             mCurrentClient.dumpHprof();
492         }
493     }
494 
toggleMethodProfiling()495     public void toggleMethodProfiling() {
496         if (mCurrentClient != null) {
497             mCurrentClient.toggleMethodProfiling();
498         }
499     }
500 
setEnabledHeapOnSelectedClient(boolean enable)501     public void setEnabledHeapOnSelectedClient(boolean enable) {
502         if (mCurrentClient != null) {
503             mCurrentClient.setHeapUpdateEnabled(enable);
504         }
505     }
506 
setEnabledThreadOnSelectedClient(boolean enable)507     public void setEnabledThreadOnSelectedClient(boolean enable) {
508         if (mCurrentClient != null) {
509             mCurrentClient.setThreadUpdateEnabled(enable);
510         }
511     }
512 
513     /**
514      * Sent when a new {@link AndroidDebugBridge} is started.
515      * <p/>
516      * This is sent from a non UI thread.
517      * @param bridge the new {@link AndroidDebugBridge} object.
518      *
519      * @see IDebugBridgeChangeListener#serverChanged(AndroidDebugBridge)
520      */
521     @Override
bridgeChanged(final AndroidDebugBridge bridge)522     public void bridgeChanged(final AndroidDebugBridge bridge) {
523         if (mTree.isDisposed() == false) {
524             exec(new Runnable() {
525                 @Override
526                 public void run() {
527                     if (mTree.isDisposed() == false) {
528                         // set up the data source.
529                         mTreeViewer.setInput(bridge);
530 
531                         // notify the listener of a possible selection change.
532                         notifyListeners();
533                     } else {
534                         // tree is disposed, we need to do something.
535                         // lets remove ourselves from the listener.
536                         AndroidDebugBridge.removeDebugBridgeChangeListener(DevicePanel.this);
537                         AndroidDebugBridge.removeDeviceChangeListener(DevicePanel.this);
538                         AndroidDebugBridge.removeClientChangeListener(DevicePanel.this);
539                     }
540                 }
541             });
542         }
543 
544         // all current devices are obsolete
545         synchronized (mDevicesToExpand) {
546             mDevicesToExpand.clear();
547         }
548     }
549 
550     /**
551      * Sent when the a device is connected to the {@link AndroidDebugBridge}.
552      * <p/>
553      * This is sent from a non UI thread.
554      * @param device the new device.
555      *
556      * @see IDeviceChangeListener#deviceConnected(IDevice)
557      */
558     @Override
deviceConnected(IDevice device)559     public void deviceConnected(IDevice device) {
560         exec(new Runnable() {
561             @Override
562             public void run() {
563                 if (mTree.isDisposed() == false) {
564                     // refresh all
565                     mTreeViewer.refresh();
566 
567                     // notify the listener of a possible selection change.
568                     notifyListeners();
569                 } else {
570                     // tree is disposed, we need to do something.
571                     // lets remove ourselves from the listener.
572                     AndroidDebugBridge.removeDebugBridgeChangeListener(DevicePanel.this);
573                     AndroidDebugBridge.removeDeviceChangeListener(DevicePanel.this);
574                     AndroidDebugBridge.removeClientChangeListener(DevicePanel.this);
575                 }
576             }
577         });
578 
579         // if it doesn't have clients yet, it'll need to be manually expanded when it gets them.
580         if (device.hasClients() == false) {
581             synchronized (mDevicesToExpand) {
582                 mDevicesToExpand.add(device);
583             }
584         }
585     }
586 
587     /**
588      * Sent when the a device is connected to the {@link AndroidDebugBridge}.
589      * <p/>
590      * This is sent from a non UI thread.
591      * @param device the new device.
592      *
593      * @see IDeviceChangeListener#deviceDisconnected(IDevice)
594      */
595     @Override
deviceDisconnected(IDevice device)596     public void deviceDisconnected(IDevice device) {
597         deviceConnected(device);
598 
599         // just in case, we remove it from the list of devices to expand.
600         synchronized (mDevicesToExpand) {
601             mDevicesToExpand.remove(device);
602         }
603     }
604 
605     /**
606      * Sent when a device data changed, or when clients are started/terminated on the device.
607      * <p/>
608      * This is sent from a non UI thread.
609      * @param device the device that was updated.
610      * @param changeMask the mask indicating what changed.
611      *
612      * @see IDeviceChangeListener#deviceChanged(IDevice)
613      */
614     @Override
deviceChanged(final IDevice device, int changeMask)615     public void deviceChanged(final IDevice device, int changeMask) {
616         boolean expand = false;
617         synchronized (mDevicesToExpand) {
618             int index = mDevicesToExpand.indexOf(device);
619             if (device.hasClients() && index != -1) {
620                 mDevicesToExpand.remove(index);
621                 expand = true;
622             }
623         }
624 
625         final boolean finalExpand = expand;
626 
627         exec(new Runnable() {
628             @Override
629             public void run() {
630                 if (mTree.isDisposed() == false) {
631                     // look if the current device is selected. This is done in case the current
632                     // client of this particular device was killed. In this case, we'll need to
633                     // manually reselect the device.
634 
635                     IDevice selectedDevice = getSelectedDevice();
636 
637                     // refresh the device
638                     mTreeViewer.refresh(device);
639 
640                     // if the selected device was the changed device and the new selection is
641                     // empty, we reselect the device.
642                     if (selectedDevice == device && mTreeViewer.getSelection().isEmpty()) {
643                         mTreeViewer.setSelection(new TreeSelection(new TreePath(
644                                 new Object[] { device })));
645                     }
646 
647                     // notify the listener of a possible selection change.
648                     notifyListeners();
649 
650                     if (finalExpand) {
651                         mTreeViewer.setExpandedState(device, true);
652                     }
653                 } else {
654                     // tree is disposed, we need to do something.
655                     // lets remove ourselves from the listener.
656                     AndroidDebugBridge.removeDebugBridgeChangeListener(DevicePanel.this);
657                     AndroidDebugBridge.removeDeviceChangeListener(DevicePanel.this);
658                     AndroidDebugBridge.removeClientChangeListener(DevicePanel.this);
659                 }
660             }
661         });
662     }
663 
664     /**
665      * Sent when an existing client information changed.
666      * <p/>
667      * This is sent from a non UI thread.
668      * @param client the updated client.
669      * @param changeMask the bit mask describing the changed properties. It can contain
670      * any of the following values: {@link Client#CHANGE_INFO},
671      * {@link Client#CHANGE_DEBUGGER_STATUS}, {@link Client#CHANGE_THREAD_MODE},
672      * {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE},
673      * {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA}
674      *
675      * @see IClientChangeListener#clientChanged(Client, int)
676      */
677     @Override
clientChanged(final Client client, final int changeMask)678     public void clientChanged(final Client client, final int changeMask) {
679         exec(new Runnable() {
680             @Override
681             public void run() {
682                 if (mTree.isDisposed() == false) {
683                     // refresh the client
684                     mTreeViewer.refresh(client);
685 
686                     if ((changeMask & Client.CHANGE_DEBUGGER_STATUS) ==
687                             Client.CHANGE_DEBUGGER_STATUS &&
688                             client.getClientData().getDebuggerConnectionStatus() ==
689                                 DebuggerStatus.WAITING) {
690                         // make sure the device is expanded. Normally the setSelection below
691                         // will auto expand, but the children of device may not already exist
692                         // at this time. Forcing an expand will make the TreeViewer create them.
693                         IDevice device = client.getDevice();
694                         if (mTreeViewer.getExpandedState(device) == false) {
695                             mTreeViewer.setExpandedState(device, true);
696                         }
697 
698                         // create and set the selection
699                         TreePath treePath = new TreePath(new Object[] { device, client});
700                         TreeSelection treeSelection = new TreeSelection(treePath);
701                         mTreeViewer.setSelection(treeSelection);
702 
703                         if (mAdvancedPortSupport) {
704                             client.setAsSelectedClient();
705                         }
706 
707                         // notify the listener of a possible selection change.
708                         notifyListeners(device, client);
709                     }
710                 } else {
711                     // tree is disposed, we need to do something.
712                     // lets remove ourselves from the listener.
713                     AndroidDebugBridge.removeDebugBridgeChangeListener(DevicePanel.this);
714                     AndroidDebugBridge.removeDeviceChangeListener(DevicePanel.this);
715                     AndroidDebugBridge.removeClientChangeListener(DevicePanel.this);
716                 }
717             }
718         });
719     }
720 
loadImages(Display display)721     private void loadImages(Display display) {
722         ImageLoader loader = ImageLoader.getDdmUiLibLoader();
723 
724         if (mDeviceImage == null) {
725             mDeviceImage = loader.loadImage(display, "device.png", //$NON-NLS-1$
726                     ICON_WIDTH, ICON_WIDTH,
727                     display.getSystemColor(SWT.COLOR_RED));
728         }
729         if (mEmulatorImage == null) {
730             mEmulatorImage = loader.loadImage(display,
731                     "emulator.png", ICON_WIDTH, ICON_WIDTH, //$NON-NLS-1$
732                     display.getSystemColor(SWT.COLOR_BLUE));
733         }
734         if (mThreadImage == null) {
735             mThreadImage = loader.loadImage(display, ICON_THREAD,
736                     ICON_WIDTH, ICON_WIDTH,
737                     display.getSystemColor(SWT.COLOR_YELLOW));
738         }
739         if (mHeapImage == null) {
740             mHeapImage = loader.loadImage(display, ICON_HEAP,
741                     ICON_WIDTH, ICON_WIDTH,
742                     display.getSystemColor(SWT.COLOR_BLUE));
743         }
744         if (mWaitingImage == null) {
745             mWaitingImage = loader.loadImage(display,
746                     "debug-wait.png", ICON_WIDTH, ICON_WIDTH, //$NON-NLS-1$
747                     display.getSystemColor(SWT.COLOR_RED));
748         }
749         if (mDebuggerImage == null) {
750             mDebuggerImage = loader.loadImage(display,
751                     "debug-attach.png", ICON_WIDTH, ICON_WIDTH, //$NON-NLS-1$
752                     display.getSystemColor(SWT.COLOR_GREEN));
753         }
754         if (mDebugErrorImage == null) {
755             mDebugErrorImage = loader.loadImage(display,
756                     "debug-error.png", ICON_WIDTH, ICON_WIDTH, //$NON-NLS-1$
757                     display.getSystemColor(SWT.COLOR_RED));
758         }
759     }
760 
761     /**
762      * Returns a display string representing the state of the device.
763      * @param d the device
764      */
getStateString(IDevice d)765     private static String getStateString(IDevice d) {
766         DeviceState deviceState = d.getState();
767         if (deviceState == DeviceState.ONLINE) {
768             return "Online";
769         } else if (deviceState == DeviceState.OFFLINE) {
770             return "Offline";
771         } else if (deviceState == DeviceState.BOOTLOADER) {
772             return "Bootloader";
773         }
774 
775         return "??";
776     }
777 
778     /**
779      * Executes the {@link Runnable} in the UI thread.
780      * @param runnable the runnable to execute.
781      */
exec(Runnable runnable)782     private void exec(Runnable runnable) {
783         try {
784             Display display = mTree.getDisplay();
785             display.asyncExec(runnable);
786         } catch (SWTException e) {
787             // tree is disposed, we need to do something. lets remove ourselves from the listener.
788             AndroidDebugBridge.removeDebugBridgeChangeListener(this);
789             AndroidDebugBridge.removeDeviceChangeListener(this);
790             AndroidDebugBridge.removeClientChangeListener(this);
791         }
792     }
793 
notifyListeners()794     private void notifyListeners() {
795         // get the selection
796         TreeItem[] items = mTree.getSelection();
797 
798         Client client = null;
799         IDevice device = null;
800 
801         if (items.length == 1) {
802             Object object = items[0].getData();
803             if (object instanceof Client) {
804                 client = (Client)object;
805                 device = client.getDevice();
806             } else if (object instanceof IDevice) {
807                 device = (IDevice)object;
808             }
809         }
810 
811         notifyListeners(device, client);
812     }
813 
notifyListeners(IDevice selectedDevice, Client selectedClient)814     private void notifyListeners(IDevice selectedDevice, Client selectedClient) {
815         if (selectedDevice != mCurrentDevice || selectedClient != mCurrentClient) {
816             mCurrentDevice = selectedDevice;
817             mCurrentClient = selectedClient;
818 
819             for (IUiSelectionListener listener : mListeners) {
820                 // notify the listener with a try/catch-all to make sure this thread won't die
821                 // because of an uncaught exception before all the listeners were notified.
822                 try {
823                     listener.selectionChanged(selectedDevice, selectedClient);
824                 } catch (Exception e) {
825                 }
826             }
827         }
828     }
829 
830 }
831