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