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 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 120 public Object getParent(Object element) { 121 if (element instanceof Client) { 122 return ((Client)element).getDevice(); 123 } 124 return null; 125 } 126 127 @Override 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 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 146 public void dispose() { 147 // pass 148 } 149 150 @Override 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 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 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 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 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 323 public void addListener(ILabelProviderListener listener) { 324 // pass 325 } 326 327 @Override 328 public void dispose() { 329 // pass 330 } 331 332 @Override 333 public boolean isLabelProperty(Object element, String property) { 334 // pass 335 return false; 336 } 337 338 @Override 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 */ 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 */ 363 public DevicePanel(boolean advancedPortSupport) { 364 mAdvancedPortSupport = advancedPortSupport; 365 } 366 367 public void addSelectionListener(IUiSelectionListener listener) { 368 mListeners.add(listener); 369 } 370 371 public void removeSelectionListener(IUiSelectionListener listener) { 372 mListeners.remove(listener); 373 } 374 375 @Override 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 430 public void setFocus() { 431 mTree.setFocus(); 432 } 433 434 @Override 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 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 */ 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 */ 460 public IDevice getSelectedDevice() { 461 return mCurrentDevice; 462 } 463 464 /** 465 * Kills the selected {@link Client} by sending its VM a halt command. 466 */ 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 */ 483 public void forceGcOnSelectedClient() { 484 if (mCurrentClient != null) { 485 mCurrentClient.executeGarbageCollector(); 486 } 487 } 488 489 public void dumpHprof() { 490 if (mCurrentClient != null) { 491 mCurrentClient.dumpHprof(); 492 } 493 } 494 495 public void toggleMethodProfiling() { 496 if (mCurrentClient != null) { 497 mCurrentClient.toggleMethodProfiling(); 498 } 499 } 500 501 public void setEnabledHeapOnSelectedClient(boolean enable) { 502 if (mCurrentClient != null) { 503 mCurrentClient.setHeapUpdateEnabled(enable); 504 } 505 } 506 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 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 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 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 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 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 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 */ 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 */ 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 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 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