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