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.ddms; 18 19 import com.android.ddmlib.AndroidDebugBridge; 20 import com.android.ddmlib.Client; 21 import com.android.ddmlib.ClientData; 22 import com.android.ddmlib.IDevice; 23 import com.android.ddmlib.Log; 24 import com.android.ddmlib.SyncService; 25 import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener; 26 import com.android.ddmlib.ClientData.IHprofDumpHandler; 27 import com.android.ddmlib.ClientData.MethodProfilingStatus; 28 import com.android.ddmlib.Log.ILogOutput; 29 import com.android.ddmlib.Log.LogLevel; 30 import com.android.ddmlib.SyncService.SyncResult; 31 import com.android.ddmuilib.AllocationPanel; 32 import com.android.ddmuilib.DevicePanel; 33 import com.android.ddmuilib.EmulatorControlPanel; 34 import com.android.ddmuilib.HeapPanel; 35 import com.android.ddmuilib.ITableFocusListener; 36 import com.android.ddmuilib.ImageHelper; 37 import com.android.ddmuilib.ImageLoader; 38 import com.android.ddmuilib.InfoPanel; 39 import com.android.ddmuilib.NativeHeapPanel; 40 import com.android.ddmuilib.ScreenShotDialog; 41 import com.android.ddmuilib.SysinfoPanel; 42 import com.android.ddmuilib.TablePanel; 43 import com.android.ddmuilib.ThreadPanel; 44 import com.android.ddmuilib.DevicePanel.IUiSelectionListener; 45 import com.android.ddmuilib.actions.ToolItemAction; 46 import com.android.ddmuilib.explorer.DeviceExplorer; 47 import com.android.ddmuilib.handler.BaseFileHandler; 48 import com.android.ddmuilib.handler.MethodProfilingHandler; 49 import com.android.ddmuilib.log.event.EventLogPanel; 50 import com.android.ddmuilib.logcat.LogColors; 51 import com.android.ddmuilib.logcat.LogFilter; 52 import com.android.ddmuilib.logcat.LogPanel; 53 import com.android.ddmuilib.logcat.LogPanel.ILogFilterStorageManager; 54 55 import org.eclipse.jface.dialogs.MessageDialog; 56 import org.eclipse.jface.preference.IPreferenceStore; 57 import org.eclipse.jface.preference.PreferenceStore; 58 import org.eclipse.swt.SWT; 59 import org.eclipse.swt.SWTError; 60 import org.eclipse.swt.SWTException; 61 import org.eclipse.swt.dnd.Clipboard; 62 import org.eclipse.swt.events.ControlEvent; 63 import org.eclipse.swt.events.ControlListener; 64 import org.eclipse.swt.events.MenuAdapter; 65 import org.eclipse.swt.events.MenuEvent; 66 import org.eclipse.swt.events.SelectionAdapter; 67 import org.eclipse.swt.events.SelectionEvent; 68 import org.eclipse.swt.events.ShellEvent; 69 import org.eclipse.swt.events.ShellListener; 70 import org.eclipse.swt.graphics.Color; 71 import org.eclipse.swt.graphics.Font; 72 import org.eclipse.swt.graphics.FontData; 73 import org.eclipse.swt.graphics.Image; 74 import org.eclipse.swt.graphics.Rectangle; 75 import org.eclipse.swt.layout.FillLayout; 76 import org.eclipse.swt.layout.FormAttachment; 77 import org.eclipse.swt.layout.FormData; 78 import org.eclipse.swt.layout.FormLayout; 79 import org.eclipse.swt.layout.GridData; 80 import org.eclipse.swt.layout.GridLayout; 81 import org.eclipse.swt.widgets.Composite; 82 import org.eclipse.swt.widgets.Display; 83 import org.eclipse.swt.widgets.Event; 84 import org.eclipse.swt.widgets.Label; 85 import org.eclipse.swt.widgets.Listener; 86 import org.eclipse.swt.widgets.Menu; 87 import org.eclipse.swt.widgets.MenuItem; 88 import org.eclipse.swt.widgets.MessageBox; 89 import org.eclipse.swt.widgets.Sash; 90 import org.eclipse.swt.widgets.Shell; 91 import org.eclipse.swt.widgets.TabFolder; 92 import org.eclipse.swt.widgets.TabItem; 93 import org.eclipse.swt.widgets.ToolBar; 94 import org.eclipse.swt.widgets.ToolItem; 95 96 import java.io.File; 97 import java.util.ArrayList; 98 99 /** 100 * This acts as the UI builder. This cannot be its own thread since this prevent using AWT in an 101 * SWT application. So this class mainly builds the ui, and manages communication between the panels 102 * when {@link IDevice} / {@link Client} selection changes. 103 */ 104 public class UIThread implements IUiSelectionListener, IClientChangeListener { 105 /* 106 * UI tab panel definitions. The constants here must match up with the array 107 * indices in mPanels. PANEL_CLIENT_LIST is a "virtual" panel representing 108 * the client list. 109 */ 110 public static final int PANEL_CLIENT_LIST = -1; 111 112 public static final int PANEL_INFO = 0; 113 114 public static final int PANEL_THREAD = 1; 115 116 public static final int PANEL_HEAP = 2; 117 118 public static final int PANEL_NATIVE_HEAP = 3; 119 120 private static final int PANEL_ALLOCATIONS = 4; 121 122 private static final int PANEL_SYSINFO = 5; 123 124 private static final int PANEL_COUNT = 6; 125 126 /** Content is setup in the constructor */ 127 private static TablePanel[] mPanels = new TablePanel[PANEL_COUNT]; 128 129 private static final String[] mPanelNames = new String[] { 130 "Info", "Threads", "VM Heap", "Native Heap", "Allocation Tracker", "Sysinfo" 131 }; 132 133 private static final String[] mPanelTips = new String[] { 134 "Client information", "Thread status", "VM heap status", 135 "Native heap status", "Allocation Tracker", "Sysinfo graphs" 136 }; 137 138 private static final String PREFERENCE_LOGSASH = 139 "logSashLocation"; //$NON-NLS-1$ 140 private static final String PREFERENCE_SASH = 141 "sashLocation"; //$NON-NLS-1$ 142 143 private static final String PREFS_COL_TIME = 144 "logcat.time"; //$NON-NLS-1$ 145 private static final String PREFS_COL_LEVEL = 146 "logcat.level"; //$NON-NLS-1$ 147 private static final String PREFS_COL_PID = 148 "logcat.pid"; //$NON-NLS-1$ 149 private static final String PREFS_COL_TAG = 150 "logcat.tag"; //$NON-NLS-1$ 151 private static final String PREFS_COL_MESSAGE = 152 "logcat.message"; //$NON-NLS-1$ 153 154 private static final String PREFS_FILTERS = "logcat.filter"; //$NON-NLS-1$ 155 156 // singleton instance 157 private static UIThread mInstance = new UIThread(); 158 159 // our display 160 private Display mDisplay; 161 162 // the table we show in the left-hand pane 163 private DevicePanel mDevicePanel; 164 165 private IDevice mCurrentDevice = null; 166 private Client mCurrentClient = null; 167 168 // status line at the bottom of the app window 169 private Label mStatusLine; 170 171 // some toolbar items we need to update 172 private ToolItem mTBShowThreadUpdates; 173 private ToolItem mTBShowHeapUpdates; 174 private ToolItem mTBHalt; 175 private ToolItem mTBCauseGc; 176 private ToolItem mTBDumpHprof; 177 private ToolItem mTBProfiling; 178 179 private ImageLoader mDdmsImageLoader; 180 private ImageLoader mDdmuiLibImageLoader; 181 182 private final class FilterStorage implements ILogFilterStorageManager { 183 getFilterFromStore()184 public LogFilter[] getFilterFromStore() { 185 String filterPrefs = PrefsDialog.getStore().getString( 186 PREFS_FILTERS); 187 188 // split in a string per filter 189 String[] filters = filterPrefs.split("\\|"); //$NON-NLS-1$ 190 191 ArrayList<LogFilter> list = 192 new ArrayList<LogFilter>(filters.length); 193 194 for (String f : filters) { 195 if (f.length() > 0) { 196 LogFilter logFilter = new LogFilter(); 197 if (logFilter.loadFromString(f)) { 198 list.add(logFilter); 199 } 200 } 201 } 202 203 return list.toArray(new LogFilter[list.size()]); 204 } 205 saveFilters(LogFilter[] filters)206 public void saveFilters(LogFilter[] filters) { 207 StringBuilder sb = new StringBuilder(); 208 for (LogFilter f : filters) { 209 String filterString = f.toString(); 210 sb.append(filterString); 211 sb.append('|'); 212 } 213 214 PrefsDialog.getStore().setValue(PREFS_FILTERS, sb.toString()); 215 } 216 requiresDefaultFilter()217 public boolean requiresDefaultFilter() { 218 return true; 219 } 220 } 221 222 223 private LogPanel mLogPanel; 224 225 private ToolItemAction mCreateFilterAction; 226 private ToolItemAction mDeleteFilterAction; 227 private ToolItemAction mEditFilterAction; 228 private ToolItemAction mExportAction; 229 private ToolItemAction mClearAction; 230 231 private ToolItemAction[] mLogLevelActions; 232 private String[] mLogLevelIcons = { 233 "v.png", //$NON-NLS-1S 234 "d.png", //$NON-NLS-1S 235 "i.png", //$NON-NLS-1S 236 "w.png", //$NON-NLS-1S 237 "e.png", //$NON-NLS-1S 238 }; 239 240 protected Clipboard mClipboard; 241 242 private MenuItem mCopyMenuItem; 243 244 private MenuItem mSelectAllMenuItem; 245 246 private TableFocusListener mTableListener; 247 248 private DeviceExplorer mExplorer = null; 249 private Shell mExplorerShell = null; 250 251 private EmulatorControlPanel mEmulatorPanel; 252 253 private EventLogPanel mEventLogPanel; 254 255 private Image mTracingStartImage; 256 257 private Image mTracingStopImage; 258 259 private class TableFocusListener implements ITableFocusListener { 260 261 private IFocusedTableActivator mCurrentActivator; 262 focusGained(IFocusedTableActivator activator)263 public void focusGained(IFocusedTableActivator activator) { 264 mCurrentActivator = activator; 265 if (mCopyMenuItem.isDisposed() == false) { 266 mCopyMenuItem.setEnabled(true); 267 mSelectAllMenuItem.setEnabled(true); 268 } 269 } 270 focusLost(IFocusedTableActivator activator)271 public void focusLost(IFocusedTableActivator activator) { 272 // if we move from one table to another, it's unclear 273 // if the old table lose its focus before the new 274 // one gets the focus, so we need to check. 275 if (activator == mCurrentActivator) { 276 activator = null; 277 if (mCopyMenuItem.isDisposed() == false) { 278 mCopyMenuItem.setEnabled(false); 279 mSelectAllMenuItem.setEnabled(false); 280 } 281 } 282 } 283 copy(Clipboard clipboard)284 public void copy(Clipboard clipboard) { 285 if (mCurrentActivator != null) { 286 mCurrentActivator.copy(clipboard); 287 } 288 } 289 selectAll()290 public void selectAll() { 291 if (mCurrentActivator != null) { 292 mCurrentActivator.selectAll(); 293 } 294 } 295 296 } 297 298 /** 299 * Handler for HPROF dumps. 300 * This will always prompt the user to save the HPROF file. 301 */ 302 private class HProfHandler extends BaseFileHandler implements IHprofDumpHandler { 303 HProfHandler(Shell parentShell)304 public HProfHandler(Shell parentShell) { 305 super(parentShell); 306 } 307 onFailure(final Client client)308 public void onFailure(final Client client) { 309 mDisplay.asyncExec(new Runnable() { 310 public void run() { 311 try { 312 displayError("Unable to create HPROF file for application '%1$s'.\n" + 313 "Check logcat for more information.", 314 client.getClientData().getClientDescription()); 315 } finally { 316 // this will make sure the dump hprof button is re-enabled for the 317 // current selection. as the client is finished dumping an hprof file 318 enableButtons(); 319 } 320 } 321 }); 322 } 323 onSuccess(final String remoteFilePath, final Client client)324 public void onSuccess(final String remoteFilePath, final Client client) { 325 mDisplay.asyncExec(new Runnable() { 326 public void run() { 327 final IDevice device = client.getDevice(); 328 try { 329 // get the sync service to pull the HPROF file 330 final SyncService sync = client.getDevice().getSyncService(); 331 if (sync != null) { 332 SyncResult result = promptAndPull(sync, 333 client.getClientData().getClientDescription() + ".hprof", 334 remoteFilePath, "Save HPROF file"); 335 if (result != null && result.getCode() != SyncService.RESULT_OK) { 336 displayError( 337 "Unable to download HPROF file from device '%1$s'.\n\n%2$s", 338 device.getSerialNumber(), result.getMessage()); 339 } 340 } else { 341 displayError("Unable to download HPROF file from device '%1$s'.", 342 device.getSerialNumber()); 343 } 344 } catch (Exception e) { 345 displayError("Unable to download HPROF file from device '%1$s'.", 346 device.getSerialNumber()); 347 348 } finally { 349 // this will make sure the dump hprof button is re-enabled for the 350 // current selection. as the client is finished dumping an hprof file 351 enableButtons(); 352 } 353 } 354 }); 355 } 356 displayError(String format, Object... args)357 private void displayError(String format, Object... args) { 358 MessageDialog.openError(mParentShell, "HPROF Error", 359 String.format(format, args)); 360 } 361 } 362 363 364 /** 365 * Generic constructor. 366 */ UIThread()367 private UIThread() { 368 mPanels[PANEL_INFO] = new InfoPanel(); 369 mPanels[PANEL_THREAD] = new ThreadPanel(); 370 mPanels[PANEL_HEAP] = new HeapPanel(); 371 if (PrefsDialog.getStore().getBoolean(PrefsDialog.SHOW_NATIVE_HEAP)) { 372 mPanels[PANEL_NATIVE_HEAP] = new NativeHeapPanel(); 373 } else { 374 mPanels[PANEL_NATIVE_HEAP] = null; 375 } 376 mPanels[PANEL_ALLOCATIONS] = new AllocationPanel(); 377 mPanels[PANEL_SYSINFO] = new SysinfoPanel(); 378 } 379 380 /** 381 * Get singleton instance of the UI thread. 382 */ getInstance()383 public static UIThread getInstance() { 384 return mInstance; 385 } 386 387 /** 388 * Return the Display. Don't try this unless you're in the UI thread. 389 */ getDisplay()390 public Display getDisplay() { 391 return mDisplay; 392 } 393 asyncExec(Runnable r)394 public void asyncExec(Runnable r) { 395 if (mDisplay != null && mDisplay.isDisposed() == false) { 396 mDisplay.asyncExec(r); 397 } 398 } 399 400 /** returns the IPreferenceStore */ getStore()401 public IPreferenceStore getStore() { 402 return PrefsDialog.getStore(); 403 } 404 405 /** 406 * Create SWT objects and drive the user interface event loop. 407 * @param location location of the folder that contains ddms. 408 */ runUI(String ddmsParentLocation)409 public void runUI(String ddmsParentLocation) { 410 Display.setAppName("ddms"); 411 mDisplay = new Display(); 412 final Shell shell = new Shell(mDisplay); 413 414 // create the image loaders for DDMS and DDMUILIB 415 mDdmsImageLoader = new ImageLoader(this.getClass()); 416 mDdmuiLibImageLoader = new ImageLoader(DevicePanel.class); 417 418 shell.setImage(ImageHelper.loadImage(mDdmsImageLoader, mDisplay, 419 "ddms-icon.png", //$NON-NLS-1$ 420 100, 50, null)); 421 422 Log.setLogOutput(new ILogOutput() { 423 public void printAndPromptLog(final LogLevel logLevel, final String tag, 424 final String message) { 425 Log.printLog(logLevel, tag, message); 426 // dialog box only run in UI thread.. 427 mDisplay.asyncExec(new Runnable() { 428 public void run() { 429 Shell shell = mDisplay.getActiveShell(); 430 if (logLevel == LogLevel.ERROR) { 431 MessageDialog.openError(shell, tag, message); 432 } else { 433 MessageDialog.openWarning(shell, tag, message); 434 } 435 } 436 }); 437 } 438 439 public void printLog(LogLevel logLevel, String tag, String message) { 440 Log.printLog(logLevel, tag, message); 441 } 442 }); 443 444 // set the handler for hprof dump 445 ClientData.setHprofDumpHandler(new HProfHandler(shell)); 446 ClientData.setMethodProfilingHandler(new MethodProfilingHandler(shell)); 447 448 // [try to] ensure ADB is running 449 String adbLocation; 450 if (ddmsParentLocation != null && ddmsParentLocation.length() != 0) { 451 adbLocation = ddmsParentLocation + File.separator + "adb"; //$NON-NLS-1$ 452 } else { 453 adbLocation = "adb"; //$NON-NLS-1$ 454 } 455 456 AndroidDebugBridge.init(true /* debugger support */); 457 AndroidDebugBridge.createBridge(adbLocation, true /* forceNewBridge */); 458 459 // we need to listen to client change to be notified of client status (profiling) change 460 AndroidDebugBridge.addClientChangeListener(this); 461 462 shell.setText("Dalvik Debug Monitor"); 463 setConfirmClose(shell); 464 createMenus(shell); 465 createWidgets(shell); 466 467 shell.pack(); 468 setSizeAndPosition(shell); 469 shell.open(); 470 471 Log.d("ddms", "UI is up"); 472 473 while (!shell.isDisposed()) { 474 if (!mDisplay.readAndDispatch()) 475 mDisplay.sleep(); 476 } 477 mLogPanel.stopLogCat(true); 478 479 mDevicePanel.dispose(); 480 for (TablePanel panel : mPanels) { 481 if (panel != null) { 482 panel.dispose(); 483 } 484 } 485 486 mDisplay.dispose(); 487 Log.d("ddms", "UI is down"); 488 } 489 490 /** 491 * Set the size and position of the main window from the preference, and 492 * setup listeners for control events (resize/move of the window) 493 */ setSizeAndPosition(final Shell shell)494 private void setSizeAndPosition(final Shell shell) { 495 shell.setMinimumSize(400, 200); 496 497 // get the x/y and w/h from the prefs 498 PreferenceStore prefs = PrefsDialog.getStore(); 499 int x = prefs.getInt(PrefsDialog.SHELL_X); 500 int y = prefs.getInt(PrefsDialog.SHELL_Y); 501 int w = prefs.getInt(PrefsDialog.SHELL_WIDTH); 502 int h = prefs.getInt(PrefsDialog.SHELL_HEIGHT); 503 504 // check that we're not out of the display area 505 Rectangle rect = mDisplay.getClientArea(); 506 // first check the width/height 507 if (w > rect.width) { 508 w = rect.width; 509 prefs.setValue(PrefsDialog.SHELL_WIDTH, rect.width); 510 } 511 if (h > rect.height) { 512 h = rect.height; 513 prefs.setValue(PrefsDialog.SHELL_HEIGHT, rect.height); 514 } 515 // then check x. Make sure the left corner is in the screen 516 if (x < rect.x) { 517 x = rect.x; 518 prefs.setValue(PrefsDialog.SHELL_X, rect.x); 519 } else if (x >= rect.x + rect.width) { 520 x = rect.x + rect.width - w; 521 prefs.setValue(PrefsDialog.SHELL_X, rect.x); 522 } 523 // then check y. Make sure the left corner is in the screen 524 if (y < rect.y) { 525 y = rect.y; 526 prefs.setValue(PrefsDialog.SHELL_Y, rect.y); 527 } else if (y >= rect.y + rect.height) { 528 y = rect.y + rect.height - h; 529 prefs.setValue(PrefsDialog.SHELL_Y, rect.y); 530 } 531 532 // now we can set the location/size 533 shell.setBounds(x, y, w, h); 534 535 // add listener for resize/move 536 shell.addControlListener(new ControlListener() { 537 public void controlMoved(ControlEvent e) { 538 // get the new x/y 539 Rectangle rect = shell.getBounds(); 540 // store in pref file 541 PreferenceStore prefs = PrefsDialog.getStore(); 542 prefs.setValue(PrefsDialog.SHELL_X, rect.x); 543 prefs.setValue(PrefsDialog.SHELL_Y, rect.y); 544 } 545 546 public void controlResized(ControlEvent e) { 547 // get the new w/h 548 Rectangle rect = shell.getBounds(); 549 // store in pref file 550 PreferenceStore prefs = PrefsDialog.getStore(); 551 prefs.setValue(PrefsDialog.SHELL_WIDTH, rect.width); 552 prefs.setValue(PrefsDialog.SHELL_HEIGHT, rect.height); 553 } 554 }); 555 } 556 557 /** 558 * Set the size and position of the file explorer window from the 559 * preference, and setup listeners for control events (resize/move of 560 * the window) 561 */ setExplorerSizeAndPosition(final Shell shell)562 private void setExplorerSizeAndPosition(final Shell shell) { 563 shell.setMinimumSize(400, 200); 564 565 // get the x/y and w/h from the prefs 566 PreferenceStore prefs = PrefsDialog.getStore(); 567 int x = prefs.getInt(PrefsDialog.EXPLORER_SHELL_X); 568 int y = prefs.getInt(PrefsDialog.EXPLORER_SHELL_Y); 569 int w = prefs.getInt(PrefsDialog.EXPLORER_SHELL_WIDTH); 570 int h = prefs.getInt(PrefsDialog.EXPLORER_SHELL_HEIGHT); 571 572 // check that we're not out of the display area 573 Rectangle rect = mDisplay.getClientArea(); 574 // first check the width/height 575 if (w > rect.width) { 576 w = rect.width; 577 prefs.setValue(PrefsDialog.EXPLORER_SHELL_WIDTH, rect.width); 578 } 579 if (h > rect.height) { 580 h = rect.height; 581 prefs.setValue(PrefsDialog.EXPLORER_SHELL_HEIGHT, rect.height); 582 } 583 // then check x. Make sure the left corner is in the screen 584 if (x < rect.x) { 585 x = rect.x; 586 prefs.setValue(PrefsDialog.EXPLORER_SHELL_X, rect.x); 587 } else if (x >= rect.x + rect.width) { 588 x = rect.x + rect.width - w; 589 prefs.setValue(PrefsDialog.EXPLORER_SHELL_X, rect.x); 590 } 591 // then check y. Make sure the left corner is in the screen 592 if (y < rect.y) { 593 y = rect.y; 594 prefs.setValue(PrefsDialog.EXPLORER_SHELL_Y, rect.y); 595 } else if (y >= rect.y + rect.height) { 596 y = rect.y + rect.height - h; 597 prefs.setValue(PrefsDialog.EXPLORER_SHELL_Y, rect.y); 598 } 599 600 // now we can set the location/size 601 shell.setBounds(x, y, w, h); 602 603 // add listener for resize/move 604 shell.addControlListener(new ControlListener() { 605 public void controlMoved(ControlEvent e) { 606 // get the new x/y 607 Rectangle rect = shell.getBounds(); 608 // store in pref file 609 PreferenceStore prefs = PrefsDialog.getStore(); 610 prefs.setValue(PrefsDialog.EXPLORER_SHELL_X, rect.x); 611 prefs.setValue(PrefsDialog.EXPLORER_SHELL_Y, rect.y); 612 } 613 614 public void controlResized(ControlEvent e) { 615 // get the new w/h 616 Rectangle rect = shell.getBounds(); 617 // store in pref file 618 PreferenceStore prefs = PrefsDialog.getStore(); 619 prefs.setValue(PrefsDialog.EXPLORER_SHELL_WIDTH, rect.width); 620 prefs.setValue(PrefsDialog.EXPLORER_SHELL_HEIGHT, rect.height); 621 } 622 }); 623 } 624 625 /* 626 * Set the confirm-before-close dialog. TODO: enable/disable in prefs. TODO: 627 * is there any point in having this? 628 */ setConfirmClose(final Shell shell)629 private void setConfirmClose(final Shell shell) { 630 if (true) 631 return; 632 633 shell.addListener(SWT.Close, new Listener() { 634 public void handleEvent(Event event) { 635 int style = SWT.APPLICATION_MODAL | SWT.YES | SWT.NO; 636 MessageBox msgBox = new MessageBox(shell, style); 637 msgBox.setText("Confirm..."); 638 msgBox.setMessage("Close DDM?"); 639 event.doit = (msgBox.open() == SWT.YES); 640 } 641 }); 642 } 643 644 /* 645 * Create the menu bar and items. 646 */ createMenus(final Shell shell)647 private void createMenus(final Shell shell) { 648 // create menu bar 649 Menu menuBar = new Menu(shell, SWT.BAR); 650 651 // create top-level items 652 MenuItem fileItem = new MenuItem(menuBar, SWT.CASCADE); 653 fileItem.setText("&File"); 654 MenuItem editItem = new MenuItem(menuBar, SWT.CASCADE); 655 editItem.setText("&Edit"); 656 MenuItem actionItem = new MenuItem(menuBar, SWT.CASCADE); 657 actionItem.setText("&Actions"); 658 MenuItem deviceItem = new MenuItem(menuBar, SWT.CASCADE); 659 deviceItem.setText("&Device"); 660 MenuItem helpItem = new MenuItem(menuBar, SWT.CASCADE); 661 helpItem.setText("&Help"); 662 663 // create top-level menus 664 Menu fileMenu = new Menu(menuBar); 665 fileItem.setMenu(fileMenu); 666 Menu editMenu = new Menu(menuBar); 667 editItem.setMenu(editMenu); 668 Menu actionMenu = new Menu(menuBar); 669 actionItem.setMenu(actionMenu); 670 Menu deviceMenu = new Menu(menuBar); 671 deviceItem.setMenu(deviceMenu); 672 Menu helpMenu = new Menu(menuBar); 673 helpItem.setMenu(helpMenu); 674 675 MenuItem item; 676 677 // create File menu items 678 item = new MenuItem(fileMenu, SWT.NONE); 679 item.setText("&Preferences..."); 680 item.addSelectionListener(new SelectionAdapter() { 681 @Override 682 public void widgetSelected(SelectionEvent e) { 683 PrefsDialog.run(shell); 684 } 685 }); 686 687 item = new MenuItem(fileMenu, SWT.NONE); 688 item.setText("&Static Port Configuration..."); 689 item.addSelectionListener(new SelectionAdapter() { 690 @Override 691 public void widgetSelected(SelectionEvent e) { 692 StaticPortConfigDialog dlg = new StaticPortConfigDialog(shell); 693 dlg.open(); 694 } 695 }); 696 697 new MenuItem(fileMenu, SWT.SEPARATOR); 698 699 item = new MenuItem(fileMenu, SWT.NONE); 700 item.setText("E&xit\tCtrl-Q"); 701 item.setAccelerator('Q' | SWT.CONTROL); 702 item.addSelectionListener(new SelectionAdapter() { 703 @Override 704 public void widgetSelected(SelectionEvent e) { 705 shell.close(); 706 } 707 }); 708 709 // create edit menu items 710 mCopyMenuItem = new MenuItem(editMenu, SWT.NONE); 711 mCopyMenuItem.setText("&Copy\tCtrl-C"); 712 mCopyMenuItem.setAccelerator('C' | SWT.COMMAND); 713 mCopyMenuItem.addSelectionListener(new SelectionAdapter() { 714 @Override 715 public void widgetSelected(SelectionEvent e) { 716 mTableListener.copy(mClipboard); 717 } 718 }); 719 720 new MenuItem(editMenu, SWT.SEPARATOR); 721 722 mSelectAllMenuItem = new MenuItem(editMenu, SWT.NONE); 723 mSelectAllMenuItem.setText("Select &All\tCtrl-A"); 724 mSelectAllMenuItem.setAccelerator('A' | SWT.COMMAND); 725 mSelectAllMenuItem.addSelectionListener(new SelectionAdapter() { 726 @Override 727 public void widgetSelected(SelectionEvent e) { 728 mTableListener.selectAll(); 729 } 730 }); 731 732 // create Action menu items 733 // TODO: this should come with a confirmation dialog 734 final MenuItem actionHaltItem = new MenuItem(actionMenu, SWT.NONE); 735 actionHaltItem.setText("&Halt VM"); 736 actionHaltItem.addSelectionListener(new SelectionAdapter() { 737 @Override 738 public void widgetSelected(SelectionEvent e) { 739 mDevicePanel.killSelectedClient(); 740 } 741 }); 742 743 final MenuItem actionCauseGcItem = new MenuItem(actionMenu, SWT.NONE); 744 actionCauseGcItem.setText("Cause &GC"); 745 actionCauseGcItem.addSelectionListener(new SelectionAdapter() { 746 @Override 747 public void widgetSelected(SelectionEvent e) { 748 mDevicePanel.forceGcOnSelectedClient(); 749 } 750 }); 751 752 // configure Action items based on current state 753 actionMenu.addMenuListener(new MenuAdapter() { 754 @Override 755 public void menuShown(MenuEvent e) { 756 actionHaltItem.setEnabled(mTBHalt.getEnabled() && mCurrentClient != null); 757 actionCauseGcItem.setEnabled(mTBCauseGc.getEnabled() && mCurrentClient != null); 758 } 759 }); 760 761 // create Device menu items 762 final MenuItem screenShotItem = new MenuItem(deviceMenu, SWT.NONE); 763 screenShotItem.setText("&Screen capture...\tCTrl-S"); 764 screenShotItem.setAccelerator('S' | SWT.CONTROL); 765 screenShotItem.addSelectionListener(new SelectionAdapter() { 766 @Override 767 public void widgetSelected(SelectionEvent e) { 768 if (mCurrentDevice != null) { 769 ScreenShotDialog dlg = new ScreenShotDialog(shell); 770 dlg.open(mCurrentDevice); 771 } 772 } 773 }); 774 775 new MenuItem(deviceMenu, SWT.SEPARATOR); 776 777 final MenuItem explorerItem = new MenuItem(deviceMenu, SWT.NONE); 778 explorerItem.setText("File Explorer..."); 779 explorerItem.addSelectionListener(new SelectionAdapter() { 780 @Override 781 public void widgetSelected(SelectionEvent e) { 782 createFileExplorer(); 783 } 784 }); 785 786 new MenuItem(deviceMenu, SWT.SEPARATOR); 787 788 final MenuItem processItem = new MenuItem(deviceMenu, SWT.NONE); 789 processItem.setText("Show &process status..."); 790 processItem.addSelectionListener(new SelectionAdapter() { 791 @Override 792 public void widgetSelected(SelectionEvent e) { 793 DeviceCommandDialog dlg; 794 dlg = new DeviceCommandDialog("ps -x", "ps-x.txt", shell); 795 dlg.open(mCurrentDevice); 796 } 797 }); 798 799 final MenuItem deviceStateItem = new MenuItem(deviceMenu, SWT.NONE); 800 deviceStateItem.setText("Dump &device state..."); 801 deviceStateItem.addSelectionListener(new SelectionAdapter() { 802 @Override 803 public void widgetSelected(SelectionEvent e) { 804 DeviceCommandDialog dlg; 805 dlg = new DeviceCommandDialog("/system/bin/dumpstate /proc/self/fd/0", 806 "device-state.txt", shell); 807 dlg.open(mCurrentDevice); 808 } 809 }); 810 811 final MenuItem appStateItem = new MenuItem(deviceMenu, SWT.NONE); 812 appStateItem.setText("Dump &app state..."); 813 appStateItem.setEnabled(false); 814 appStateItem.addSelectionListener(new SelectionAdapter() { 815 @Override 816 public void widgetSelected(SelectionEvent e) { 817 DeviceCommandDialog dlg; 818 dlg = new DeviceCommandDialog("dumpsys", "app-state.txt", shell); 819 dlg.open(mCurrentDevice); 820 } 821 }); 822 823 final MenuItem radioStateItem = new MenuItem(deviceMenu, SWT.NONE); 824 radioStateItem.setText("Dump &radio state..."); 825 radioStateItem.addSelectionListener(new SelectionAdapter() { 826 @Override 827 public void widgetSelected(SelectionEvent e) { 828 DeviceCommandDialog dlg; 829 dlg = new DeviceCommandDialog( 830 "cat /data/logs/radio.4 /data/logs/radio.3" 831 + " /data/logs/radio.2 /data/logs/radio.1" 832 + " /data/logs/radio", 833 "radio-state.txt", shell); 834 dlg.open(mCurrentDevice); 835 } 836 }); 837 838 final MenuItem logCatItem = new MenuItem(deviceMenu, SWT.NONE); 839 logCatItem.setText("Run &logcat..."); 840 logCatItem.addSelectionListener(new SelectionAdapter() { 841 @Override 842 public void widgetSelected(SelectionEvent e) { 843 DeviceCommandDialog dlg; 844 dlg = new DeviceCommandDialog("logcat '*:d jdwp:w'", "log.txt", 845 shell); 846 dlg.open(mCurrentDevice); 847 } 848 }); 849 850 // configure Action items based on current state 851 deviceMenu.addMenuListener(new MenuAdapter() { 852 @Override 853 public void menuShown(MenuEvent e) { 854 boolean deviceEnabled = mCurrentDevice != null; 855 screenShotItem.setEnabled(deviceEnabled); 856 explorerItem.setEnabled(deviceEnabled); 857 processItem.setEnabled(deviceEnabled); 858 deviceStateItem.setEnabled(deviceEnabled); 859 appStateItem.setEnabled(deviceEnabled); 860 radioStateItem.setEnabled(deviceEnabled); 861 logCatItem.setEnabled(deviceEnabled); 862 } 863 }); 864 865 // create Help menu items 866 item = new MenuItem(helpMenu, SWT.NONE); 867 item.setText("&Contents..."); 868 item.addSelectionListener(new SelectionAdapter() { 869 @Override 870 public void widgetSelected(SelectionEvent e) { 871 int style = SWT.APPLICATION_MODAL | SWT.OK; 872 MessageBox msgBox = new MessageBox(shell, style); 873 msgBox.setText("Help!"); 874 msgBox.setMessage("Help wanted."); 875 msgBox.open(); 876 } 877 }); 878 879 new MenuItem(helpMenu, SWT.SEPARATOR); 880 881 item = new MenuItem(helpMenu, SWT.NONE); 882 item.setText("&About..."); 883 item.addSelectionListener(new SelectionAdapter() { 884 @Override 885 public void widgetSelected(SelectionEvent e) { 886 AboutDialog dlg = new AboutDialog(shell); 887 dlg.open(); 888 } 889 }); 890 891 // tell the shell to use this menu 892 shell.setMenuBar(menuBar); 893 } 894 895 /* 896 * Create the widgets in the main application window. The basic layout is a 897 * two-panel sash, with a scrolling list of VMs on the left and detailed 898 * output for a single VM on the right. 899 */ createWidgets(final Shell shell)900 private void createWidgets(final Shell shell) { 901 Color darkGray = shell.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY); 902 903 /* 904 * Create three areas: tool bar, split panels, status line 905 */ 906 shell.setLayout(new GridLayout(1, false)); 907 908 // 1. panel area 909 final Composite panelArea = new Composite(shell, SWT.BORDER); 910 911 // make the panel area absorb all space 912 panelArea.setLayoutData(new GridData(GridData.FILL_BOTH)); 913 914 // 2. status line. 915 mStatusLine = new Label(shell, SWT.NONE); 916 917 // make status line extend all the way across 918 mStatusLine.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 919 920 mStatusLine.setText("Initializing..."); 921 922 /* 923 * Configure the split-panel area. 924 */ 925 final PreferenceStore prefs = PrefsDialog.getStore(); 926 927 Composite topPanel = new Composite(panelArea, SWT.NONE); 928 final Sash sash = new Sash(panelArea, SWT.HORIZONTAL); 929 sash.setBackground(darkGray); 930 Composite bottomPanel = new Composite(panelArea, SWT.NONE); 931 932 panelArea.setLayout(new FormLayout()); 933 934 createTopPanel(topPanel, darkGray); 935 createBottomPanel(bottomPanel); 936 937 // form layout data 938 FormData data = new FormData(); 939 data.top = new FormAttachment(0, 0); 940 data.bottom = new FormAttachment(sash, 0); 941 data.left = new FormAttachment(0, 0); 942 data.right = new FormAttachment(100, 0); 943 topPanel.setLayoutData(data); 944 945 final FormData sashData = new FormData(); 946 if (prefs != null && prefs.contains(PREFERENCE_LOGSASH)) { 947 sashData.top = new FormAttachment(0, prefs.getInt( 948 PREFERENCE_LOGSASH)); 949 } else { 950 sashData.top = new FormAttachment(50,0); // 50% across 951 } 952 sashData.left = new FormAttachment(0, 0); 953 sashData.right = new FormAttachment(100, 0); 954 sash.setLayoutData(sashData); 955 956 data = new FormData(); 957 data.top = new FormAttachment(sash, 0); 958 data.bottom = new FormAttachment(100, 0); 959 data.left = new FormAttachment(0, 0); 960 data.right = new FormAttachment(100, 0); 961 bottomPanel.setLayoutData(data); 962 963 // allow resizes, but cap at minPanelWidth 964 sash.addListener(SWT.Selection, new Listener() { 965 public void handleEvent(Event e) { 966 Rectangle sashRect = sash.getBounds(); 967 Rectangle panelRect = panelArea.getClientArea(); 968 int bottom = panelRect.height - sashRect.height - 100; 969 e.y = Math.max(Math.min(e.y, bottom), 100); 970 if (e.y != sashRect.y) { 971 sashData.top = new FormAttachment(0, e.y); 972 prefs.setValue(PREFERENCE_LOGSASH, e.y); 973 panelArea.layout(); 974 } 975 } 976 }); 977 978 // add a global focus listener for all the tables 979 mTableListener = new TableFocusListener(); 980 981 // now set up the listener in the various panels 982 mLogPanel.setTableFocusListener(mTableListener); 983 mEventLogPanel.setTableFocusListener(mTableListener); 984 for (TablePanel p : mPanels) { 985 if (p != null) { 986 p.setTableFocusListener(mTableListener); 987 } 988 } 989 990 mStatusLine.setText(""); 991 } 992 993 /* 994 * Populate the tool bar. 995 */ createDevicePanelToolBar(ToolBar toolBar)996 private void createDevicePanelToolBar(ToolBar toolBar) { 997 Display display = toolBar.getDisplay(); 998 999 // add "show heap updates" button 1000 mTBShowHeapUpdates = new ToolItem(toolBar, SWT.CHECK); 1001 mTBShowHeapUpdates.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, display, 1002 DevicePanel.ICON_HEAP, DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null)); 1003 mTBShowHeapUpdates.setToolTipText("Show heap updates"); 1004 mTBShowHeapUpdates.setEnabled(false); 1005 mTBShowHeapUpdates.addSelectionListener(new SelectionAdapter() { 1006 @Override 1007 public void widgetSelected(SelectionEvent e) { 1008 if (mCurrentClient != null) { 1009 // boolean status = ((ToolItem)e.item).getSelection(); 1010 // invert previous state 1011 boolean enable = !mCurrentClient.isHeapUpdateEnabled(); 1012 mCurrentClient.setHeapUpdateEnabled(enable); 1013 } else { 1014 e.doit = false; // this has no effect? 1015 } 1016 } 1017 }); 1018 1019 // add "dump HPROF" button 1020 mTBDumpHprof = new ToolItem(toolBar, SWT.PUSH); 1021 mTBDumpHprof.setToolTipText("Dump HPROF file"); 1022 mTBDumpHprof.setEnabled(false); 1023 mTBDumpHprof.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, display, 1024 DevicePanel.ICON_HPROF, DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null)); 1025 mTBDumpHprof.addSelectionListener(new SelectionAdapter() { 1026 @Override 1027 public void widgetSelected(SelectionEvent e) { 1028 mDevicePanel.dumpHprof(); 1029 1030 // this will make sure the dump hprof button is disabled for the current selection 1031 // as the client is already dumping an hprof file 1032 enableButtons(); 1033 } 1034 }); 1035 1036 // add "cause GC" button 1037 mTBCauseGc = new ToolItem(toolBar, SWT.PUSH); 1038 mTBCauseGc.setToolTipText("Cause an immediate GC"); 1039 mTBCauseGc.setEnabled(false); 1040 mTBCauseGc.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, display, 1041 DevicePanel.ICON_GC, DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null)); 1042 mTBCauseGc.addSelectionListener(new SelectionAdapter() { 1043 @Override 1044 public void widgetSelected(SelectionEvent e) { 1045 mDevicePanel.forceGcOnSelectedClient(); 1046 } 1047 }); 1048 1049 new ToolItem(toolBar, SWT.SEPARATOR); 1050 1051 // add "show thread updates" button 1052 mTBShowThreadUpdates = new ToolItem(toolBar, SWT.CHECK); 1053 mTBShowThreadUpdates.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, display, 1054 DevicePanel.ICON_THREAD, DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null)); 1055 mTBShowThreadUpdates.setToolTipText("Show thread updates"); 1056 mTBShowThreadUpdates.setEnabled(false); 1057 mTBShowThreadUpdates.addSelectionListener(new SelectionAdapter() { 1058 @Override 1059 public void widgetSelected(SelectionEvent e) { 1060 if (mCurrentClient != null) { 1061 // boolean status = ((ToolItem)e.item).getSelection(); 1062 // invert previous state 1063 boolean enable = !mCurrentClient.isThreadUpdateEnabled(); 1064 1065 mCurrentClient.setThreadUpdateEnabled(enable); 1066 } else { 1067 e.doit = false; // this has no effect? 1068 } 1069 } 1070 }); 1071 1072 // add a start/stop method tracing 1073 mTracingStartImage = ImageHelper.loadImage(mDdmuiLibImageLoader, display, 1074 DevicePanel.ICON_TRACING_START, 1075 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null); 1076 mTracingStopImage = ImageHelper.loadImage(mDdmuiLibImageLoader, display, 1077 DevicePanel.ICON_TRACING_STOP, 1078 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null); 1079 mTBProfiling = new ToolItem(toolBar, SWT.PUSH); 1080 mTBProfiling.setToolTipText("Start Method Profiling"); 1081 mTBProfiling.setEnabled(false); 1082 mTBProfiling.setImage(mTracingStartImage); 1083 mTBProfiling.addSelectionListener(new SelectionAdapter() { 1084 @Override 1085 public void widgetSelected(SelectionEvent e) { 1086 mDevicePanel.toggleMethodProfiling(); 1087 } 1088 }); 1089 1090 new ToolItem(toolBar, SWT.SEPARATOR); 1091 1092 // add "kill VM" button; need to make this visually distinct from 1093 // the status update buttons 1094 mTBHalt = new ToolItem(toolBar, SWT.PUSH); 1095 mTBHalt.setToolTipText("Halt the target VM"); 1096 mTBHalt.setEnabled(false); 1097 mTBHalt.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, display, 1098 DevicePanel.ICON_HALT, DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null)); 1099 mTBHalt.addSelectionListener(new SelectionAdapter() { 1100 @Override 1101 public void widgetSelected(SelectionEvent e) { 1102 mDevicePanel.killSelectedClient(); 1103 } 1104 }); 1105 1106 toolBar.pack(); 1107 } 1108 createTopPanel(final Composite comp, Color darkGray)1109 private void createTopPanel(final Composite comp, Color darkGray) { 1110 final PreferenceStore prefs = PrefsDialog.getStore(); 1111 1112 comp.setLayout(new FormLayout()); 1113 1114 Composite leftPanel = new Composite(comp, SWT.NONE); 1115 final Sash sash = new Sash(comp, SWT.VERTICAL); 1116 sash.setBackground(darkGray); 1117 Composite rightPanel = new Composite(comp, SWT.NONE); 1118 1119 createLeftPanel(leftPanel); 1120 createRightPanel(rightPanel); 1121 1122 FormData data = new FormData(); 1123 data.top = new FormAttachment(0, 0); 1124 data.bottom = new FormAttachment(100, 0); 1125 data.left = new FormAttachment(0, 0); 1126 data.right = new FormAttachment(sash, 0); 1127 leftPanel.setLayoutData(data); 1128 1129 final FormData sashData = new FormData(); 1130 sashData.top = new FormAttachment(0, 0); 1131 sashData.bottom = new FormAttachment(100, 0); 1132 if (prefs != null && prefs.contains(PREFERENCE_SASH)) { 1133 sashData.left = new FormAttachment(0, prefs.getInt( 1134 PREFERENCE_SASH)); 1135 } else { 1136 // position the sash 380 from the right instead of x% (done by using 1137 // FormAttachment(x, 0)) in order to keep the sash at the same 1138 // position 1139 // from the left when the window is resized. 1140 // 380px is just enough to display the left table with no horizontal 1141 // scrollbar with the default font. 1142 sashData.left = new FormAttachment(0, 380); 1143 } 1144 sash.setLayoutData(sashData); 1145 1146 data = new FormData(); 1147 data.top = new FormAttachment(0, 0); 1148 data.bottom = new FormAttachment(100, 0); 1149 data.left = new FormAttachment(sash, 0); 1150 data.right = new FormAttachment(100, 0); 1151 rightPanel.setLayoutData(data); 1152 1153 final int minPanelWidth = 60; 1154 1155 // allow resizes, but cap at minPanelWidth 1156 sash.addListener(SWT.Selection, new Listener() { 1157 public void handleEvent(Event e) { 1158 Rectangle sashRect = sash.getBounds(); 1159 Rectangle panelRect = comp.getClientArea(); 1160 int right = panelRect.width - sashRect.width - minPanelWidth; 1161 e.x = Math.max(Math.min(e.x, right), minPanelWidth); 1162 if (e.x != sashRect.x) { 1163 sashData.left = new FormAttachment(0, e.x); 1164 prefs.setValue(PREFERENCE_SASH, e.x); 1165 comp.layout(); 1166 } 1167 } 1168 }); 1169 } 1170 createBottomPanel(final Composite comp)1171 private void createBottomPanel(final Composite comp) { 1172 final PreferenceStore prefs = PrefsDialog.getStore(); 1173 1174 // create clipboard 1175 Display display = comp.getDisplay(); 1176 mClipboard = new Clipboard(display); 1177 1178 LogColors colors = new LogColors(); 1179 1180 colors.infoColor = new Color(display, 0, 127, 0); 1181 colors.debugColor = new Color(display, 0, 0, 127); 1182 colors.errorColor = new Color(display, 255, 0, 0); 1183 colors.warningColor = new Color(display, 255, 127, 0); 1184 colors.verboseColor = new Color(display, 0, 0, 0); 1185 1186 // set the preferences names 1187 LogPanel.PREFS_TIME = PREFS_COL_TIME; 1188 LogPanel.PREFS_LEVEL = PREFS_COL_LEVEL; 1189 LogPanel.PREFS_PID = PREFS_COL_PID; 1190 LogPanel.PREFS_TAG = PREFS_COL_TAG; 1191 LogPanel.PREFS_MESSAGE = PREFS_COL_MESSAGE; 1192 1193 comp.setLayout(new GridLayout(1, false)); 1194 1195 ToolBar toolBar = new ToolBar(comp, SWT.HORIZONTAL); 1196 1197 mCreateFilterAction = new ToolItemAction(toolBar, SWT.PUSH); 1198 mCreateFilterAction.item.setToolTipText("Create Filter"); 1199 mCreateFilterAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, mDisplay, 1200 "add.png", //$NON-NLS-1$ 1201 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null)); 1202 mCreateFilterAction.item.addSelectionListener(new SelectionAdapter() { 1203 @Override 1204 public void widgetSelected(SelectionEvent e) { 1205 mLogPanel.addFilter(); 1206 } 1207 }); 1208 1209 mEditFilterAction = new ToolItemAction(toolBar, SWT.PUSH); 1210 mEditFilterAction.item.setToolTipText("Edit Filter"); 1211 mEditFilterAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, mDisplay, 1212 "edit.png", //$NON-NLS-1$ 1213 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null)); 1214 mEditFilterAction.item.addSelectionListener(new SelectionAdapter() { 1215 @Override 1216 public void widgetSelected(SelectionEvent e) { 1217 mLogPanel.editFilter(); 1218 } 1219 }); 1220 1221 mDeleteFilterAction = new ToolItemAction(toolBar, SWT.PUSH); 1222 mDeleteFilterAction.item.setToolTipText("Delete Filter"); 1223 mDeleteFilterAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, mDisplay, 1224 "delete.png", //$NON-NLS-1$ 1225 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null)); 1226 mDeleteFilterAction.item.addSelectionListener(new SelectionAdapter() { 1227 @Override 1228 public void widgetSelected(SelectionEvent e) { 1229 mLogPanel.deleteFilter(); 1230 } 1231 }); 1232 1233 1234 new ToolItem(toolBar, SWT.SEPARATOR); 1235 1236 LogLevel[] levels = LogLevel.values(); 1237 mLogLevelActions = new ToolItemAction[mLogLevelIcons.length]; 1238 for (int i = 0 ; i < mLogLevelActions.length; i++) { 1239 String name = levels[i].getStringValue(); 1240 final ToolItemAction newAction = new ToolItemAction(toolBar, SWT.CHECK); 1241 mLogLevelActions[i] = newAction; 1242 //newAction.item.setText(name); 1243 newAction.item.addSelectionListener(new SelectionAdapter() { 1244 @Override 1245 public void widgetSelected(SelectionEvent e) { 1246 // disable the other actions and record current index 1247 for (int i = 0 ; i < mLogLevelActions.length; i++) { 1248 ToolItemAction a = mLogLevelActions[i]; 1249 if (a == newAction) { 1250 a.setChecked(true); 1251 1252 // set the log level 1253 mLogPanel.setCurrentFilterLogLevel(i+2); 1254 } else { 1255 a.setChecked(false); 1256 } 1257 } 1258 } 1259 }); 1260 1261 newAction.item.setToolTipText(name); 1262 newAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, mDisplay, 1263 mLogLevelIcons[i], 1264 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null)); 1265 } 1266 1267 new ToolItem(toolBar, SWT.SEPARATOR); 1268 1269 mClearAction = new ToolItemAction(toolBar, SWT.PUSH); 1270 mClearAction.item.setToolTipText("Clear Log"); 1271 1272 mClearAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, mDisplay, 1273 "clear.png", //$NON-NLS-1$ 1274 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null)); 1275 mClearAction.item.addSelectionListener(new SelectionAdapter() { 1276 @Override 1277 public void widgetSelected(SelectionEvent e) { 1278 mLogPanel.clear(); 1279 } 1280 }); 1281 1282 new ToolItem(toolBar, SWT.SEPARATOR); 1283 1284 mExportAction = new ToolItemAction(toolBar, SWT.PUSH); 1285 mExportAction.item.setToolTipText("Export Selection As Text..."); 1286 mExportAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, mDisplay, 1287 "save.png", //$NON-NLS-1$ 1288 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null)); 1289 mExportAction.item.addSelectionListener(new SelectionAdapter() { 1290 @Override 1291 public void widgetSelected(SelectionEvent e) { 1292 mLogPanel.save(); 1293 } 1294 }); 1295 1296 1297 toolBar.pack(); 1298 1299 // now create the log view 1300 mLogPanel = new LogPanel(new ImageLoader(LogPanel.class), colors, new FilterStorage(), 1301 LogPanel.FILTER_MANUAL); 1302 1303 mLogPanel.setActions(mDeleteFilterAction, mEditFilterAction, mLogLevelActions); 1304 1305 String colMode = prefs.getString(PrefsDialog.LOGCAT_COLUMN_MODE); 1306 if (PrefsDialog.LOGCAT_COLUMN_MODE_AUTO.equals(colMode)) { 1307 mLogPanel.setColumnMode(LogPanel.COLUMN_MODE_AUTO); 1308 } 1309 1310 String fontStr = PrefsDialog.getStore().getString(PrefsDialog.LOGCAT_FONT); 1311 if (fontStr != null) { 1312 try { 1313 FontData fdat = new FontData(fontStr); 1314 mLogPanel.setFont(new Font(display, fdat)); 1315 } catch (IllegalArgumentException e) { 1316 // Looks like fontStr isn't a valid font representation. 1317 // We do nothing in this case, the logcat view will use the default font. 1318 } catch (SWTError e2) { 1319 // Looks like the Font() constructor failed. 1320 // We do nothing in this case, the logcat view will use the default font. 1321 } 1322 } 1323 1324 mLogPanel.createPanel(comp); 1325 1326 // and start the logcat 1327 mLogPanel.startLogCat(mCurrentDevice); 1328 } 1329 1330 /* 1331 * Create the contents of the left panel: a table of VMs. 1332 */ createLeftPanel(final Composite comp)1333 private void createLeftPanel(final Composite comp) { 1334 comp.setLayout(new GridLayout(1, false)); 1335 ToolBar toolBar = new ToolBar(comp, SWT.HORIZONTAL | SWT.RIGHT | SWT.WRAP); 1336 toolBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 1337 createDevicePanelToolBar(toolBar); 1338 1339 Composite c = new Composite(comp, SWT.NONE); 1340 c.setLayoutData(new GridData(GridData.FILL_BOTH)); 1341 1342 mDevicePanel = new DevicePanel(new ImageLoader(DevicePanel.class), true /* showPorts */); 1343 mDevicePanel.createPanel(c); 1344 1345 // add ourselves to the device panel selection listener 1346 mDevicePanel.addSelectionListener(this); 1347 } 1348 1349 /* 1350 * Create the contents of the right panel: tabs with VM information. 1351 */ createRightPanel(final Composite comp)1352 private void createRightPanel(final Composite comp) { 1353 TabItem item; 1354 TabFolder tabFolder; 1355 1356 comp.setLayout(new FillLayout()); 1357 1358 tabFolder = new TabFolder(comp, SWT.NONE); 1359 1360 for (int i = 0; i < mPanels.length; i++) { 1361 if (mPanels[i] != null) { 1362 item = new TabItem(tabFolder, SWT.NONE); 1363 item.setText(mPanelNames[i]); 1364 item.setToolTipText(mPanelTips[i]); 1365 item.setControl(mPanels[i].createPanel(tabFolder)); 1366 } 1367 } 1368 1369 // add the emulator control panel to the folders. 1370 item = new TabItem(tabFolder, SWT.NONE); 1371 item.setText("Emulator Control"); 1372 item.setToolTipText("Emulator Control Panel"); 1373 mEmulatorPanel = new EmulatorControlPanel(mDdmuiLibImageLoader); 1374 item.setControl(mEmulatorPanel.createPanel(tabFolder)); 1375 1376 // add the event log panel to the folders. 1377 item = new TabItem(tabFolder, SWT.NONE); 1378 item.setText("Event Log"); 1379 item.setToolTipText("Event Log"); 1380 1381 // create the composite that will hold the toolbar and the event log panel. 1382 Composite eventLogTopComposite = new Composite(tabFolder, SWT.NONE); 1383 item.setControl(eventLogTopComposite); 1384 eventLogTopComposite.setLayout(new GridLayout(1, false)); 1385 1386 // create the toolbar and the actions 1387 ToolBar toolbar = new ToolBar(eventLogTopComposite, SWT.HORIZONTAL); 1388 toolbar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 1389 1390 ToolItemAction optionsAction = new ToolItemAction(toolbar, SWT.PUSH); 1391 optionsAction.item.setToolTipText("Opens the options panel"); 1392 optionsAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, comp.getDisplay(), 1393 "edit.png", //$NON-NLS-1$ 1394 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null)); 1395 1396 ToolItemAction clearAction = new ToolItemAction(toolbar, SWT.PUSH); 1397 clearAction.item.setToolTipText("Clears the event log"); 1398 clearAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, comp.getDisplay(), 1399 "clear.png", //$NON-NLS-1$ 1400 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null)); 1401 1402 new ToolItem(toolbar, SWT.SEPARATOR); 1403 1404 ToolItemAction saveAction = new ToolItemAction(toolbar, SWT.PUSH); 1405 saveAction.item.setToolTipText("Saves the event log"); 1406 saveAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, comp.getDisplay(), 1407 "save.png", //$NON-NLS-1$ 1408 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null)); 1409 1410 ToolItemAction loadAction = new ToolItemAction(toolbar, SWT.PUSH); 1411 loadAction.item.setToolTipText("Loads an event log"); 1412 loadAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, comp.getDisplay(), 1413 "load.png", //$NON-NLS-1$ 1414 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null)); 1415 1416 ToolItemAction importBugAction = new ToolItemAction(toolbar, SWT.PUSH); 1417 importBugAction.item.setToolTipText("Imports a bug report"); 1418 importBugAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, comp.getDisplay(), 1419 "importBug.png", //$NON-NLS-1$ 1420 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null)); 1421 1422 // create the event log panel 1423 mEventLogPanel = new EventLogPanel(mDdmuiLibImageLoader); 1424 1425 // set the external actions 1426 mEventLogPanel.setActions(optionsAction, clearAction, saveAction, loadAction, 1427 importBugAction); 1428 1429 // create the panel 1430 mEventLogPanel.createPanel(eventLogTopComposite); 1431 } 1432 createFileExplorer()1433 private void createFileExplorer() { 1434 if (mExplorer == null) { 1435 mExplorerShell = new Shell(mDisplay); 1436 1437 // create the ui 1438 mExplorerShell.setLayout(new GridLayout(1, false)); 1439 1440 // toolbar + action 1441 ToolBar toolBar = new ToolBar(mExplorerShell, SWT.HORIZONTAL); 1442 toolBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 1443 1444 ToolItemAction pullAction = new ToolItemAction(toolBar, SWT.PUSH); 1445 pullAction.item.setToolTipText("Pull File from Device"); 1446 Image image = mDdmuiLibImageLoader.loadImage("pull.png", mDisplay); //$NON-NLS-1$ 1447 if (image != null) { 1448 pullAction.item.setImage(image); 1449 } else { 1450 // this is for debugging purpose when the icon is missing 1451 pullAction.item.setText("Pull"); //$NON-NLS-1$ 1452 } 1453 1454 ToolItemAction pushAction = new ToolItemAction(toolBar, SWT.PUSH); 1455 pushAction.item.setToolTipText("Push file onto Device"); 1456 image = mDdmuiLibImageLoader.loadImage("push.png", mDisplay); //$NON-NLS-1$ 1457 if (image != null) { 1458 pushAction.item.setImage(image); 1459 } else { 1460 // this is for debugging purpose when the icon is missing 1461 pushAction.item.setText("Push"); //$NON-NLS-1$ 1462 } 1463 1464 ToolItemAction deleteAction = new ToolItemAction(toolBar, SWT.PUSH); 1465 deleteAction.item.setToolTipText("Delete"); 1466 image = mDdmuiLibImageLoader.loadImage("delete.png", mDisplay); //$NON-NLS-1$ 1467 if (image != null) { 1468 deleteAction.item.setImage(image); 1469 } else { 1470 // this is for debugging purpose when the icon is missing 1471 deleteAction.item.setText("Delete"); //$NON-NLS-1$ 1472 } 1473 1474 // device explorer 1475 mExplorer = new DeviceExplorer(); 1476 1477 mExplorer.setImages(mDdmuiLibImageLoader.loadImage("file.png", mDisplay), //$NON-NLS-1$ 1478 mDdmuiLibImageLoader.loadImage("folder.png", mDisplay), //$NON-NLS-1$ 1479 mDdmuiLibImageLoader.loadImage("android.png", mDisplay), //$NON-NLS-1$ 1480 null); 1481 mExplorer.setActions(pushAction, pullAction, deleteAction); 1482 1483 pullAction.item.addSelectionListener(new SelectionAdapter() { 1484 @Override 1485 public void widgetSelected(SelectionEvent e) { 1486 mExplorer.pullSelection(); 1487 } 1488 }); 1489 pullAction.setEnabled(false); 1490 1491 pushAction.item.addSelectionListener(new SelectionAdapter() { 1492 @Override 1493 public void widgetSelected(SelectionEvent e) { 1494 mExplorer.pushIntoSelection(); 1495 } 1496 }); 1497 pushAction.setEnabled(false); 1498 1499 deleteAction.item.addSelectionListener(new SelectionAdapter() { 1500 @Override 1501 public void widgetSelected(SelectionEvent e) { 1502 mExplorer.deleteSelection(); 1503 } 1504 }); 1505 deleteAction.setEnabled(false); 1506 1507 Composite parent = new Composite(mExplorerShell, SWT.NONE); 1508 parent.setLayoutData(new GridData(GridData.FILL_BOTH)); 1509 1510 mExplorer.createPanel(parent); 1511 mExplorer.switchDevice(mCurrentDevice); 1512 1513 mExplorerShell.addShellListener(new ShellListener() { 1514 public void shellActivated(ShellEvent e) { 1515 // pass 1516 } 1517 1518 public void shellClosed(ShellEvent e) { 1519 mExplorer = null; 1520 mExplorerShell = null; 1521 } 1522 1523 public void shellDeactivated(ShellEvent e) { 1524 // pass 1525 } 1526 1527 public void shellDeiconified(ShellEvent e) { 1528 // pass 1529 } 1530 1531 public void shellIconified(ShellEvent e) { 1532 // pass 1533 } 1534 }); 1535 1536 mExplorerShell.pack(); 1537 setExplorerSizeAndPosition(mExplorerShell); 1538 mExplorerShell.open(); 1539 } else { 1540 if (mExplorerShell != null) { 1541 mExplorerShell.forceActive(); 1542 } 1543 } 1544 } 1545 1546 /** 1547 * Set the status line. TODO: make this a stack, so we can safely have 1548 * multiple things trying to set it all at once. Also specify an expiration? 1549 */ setStatusLine(final String str)1550 public void setStatusLine(final String str) { 1551 try { 1552 mDisplay.asyncExec(new Runnable() { 1553 public void run() { 1554 doSetStatusLine(str); 1555 } 1556 }); 1557 } catch (SWTException swte) { 1558 if (!mDisplay.isDisposed()) 1559 throw swte; 1560 } 1561 } 1562 doSetStatusLine(String str)1563 private void doSetStatusLine(String str) { 1564 if (mStatusLine.isDisposed()) 1565 return; 1566 1567 if (!mStatusLine.getText().equals(str)) { 1568 mStatusLine.setText(str); 1569 1570 // try { Thread.sleep(100); } 1571 // catch (InterruptedException ie) {} 1572 } 1573 } 1574 displayError(final String msg)1575 public void displayError(final String msg) { 1576 try { 1577 mDisplay.syncExec(new Runnable() { 1578 public void run() { 1579 MessageDialog.openError(mDisplay.getActiveShell(), "Error", 1580 msg); 1581 } 1582 }); 1583 } catch (SWTException swte) { 1584 if (!mDisplay.isDisposed()) 1585 throw swte; 1586 } 1587 } 1588 enableButtons()1589 private void enableButtons() { 1590 if (mCurrentClient != null) { 1591 mTBShowThreadUpdates.setSelection(mCurrentClient.isThreadUpdateEnabled()); 1592 mTBShowThreadUpdates.setEnabled(true); 1593 mTBShowHeapUpdates.setSelection(mCurrentClient.isHeapUpdateEnabled()); 1594 mTBShowHeapUpdates.setEnabled(true); 1595 mTBHalt.setEnabled(true); 1596 mTBCauseGc.setEnabled(true); 1597 1598 ClientData data = mCurrentClient.getClientData(); 1599 1600 if (data.hasFeature(ClientData.FEATURE_HPROF)) { 1601 mTBDumpHprof.setEnabled(data.hasPendingHprofDump() == false); 1602 mTBDumpHprof.setToolTipText("Dump HPROF file"); 1603 } else { 1604 mTBDumpHprof.setEnabled(false); 1605 mTBDumpHprof.setToolTipText("Dump HPROF file (not supported by this VM)"); 1606 } 1607 1608 if (data.hasFeature(ClientData.FEATURE_PROFILING)) { 1609 mTBProfiling.setEnabled(true); 1610 if (data.getMethodProfilingStatus() == MethodProfilingStatus.ON) { 1611 mTBProfiling.setToolTipText("Stop Method Profiling"); 1612 mTBProfiling.setImage(mTracingStopImage); 1613 } else { 1614 mTBProfiling.setToolTipText("Start Method Profiling"); 1615 mTBProfiling.setImage(mTracingStartImage); 1616 } 1617 } else { 1618 mTBProfiling.setEnabled(false); 1619 mTBProfiling.setImage(mTracingStartImage); 1620 mTBProfiling.setToolTipText("Start Method Profiling (not supported by this VM)"); 1621 } 1622 } else { 1623 // list is empty, disable these 1624 mTBShowThreadUpdates.setSelection(false); 1625 mTBShowThreadUpdates.setEnabled(false); 1626 mTBShowHeapUpdates.setSelection(false); 1627 mTBShowHeapUpdates.setEnabled(false); 1628 mTBHalt.setEnabled(false); 1629 mTBCauseGc.setEnabled(false); 1630 1631 mTBDumpHprof.setEnabled(false); 1632 mTBDumpHprof.setToolTipText("Dump HPROF file"); 1633 1634 mTBProfiling.setEnabled(false); 1635 mTBProfiling.setImage(mTracingStartImage); 1636 mTBProfiling.setToolTipText("Start Method Profiling"); 1637 } 1638 } 1639 1640 /** 1641 * Sent when a new {@link IDevice} and {@link Client} are selected. 1642 * @param selectedDevice the selected device. If null, no devices are selected. 1643 * @param selectedClient The selected client. If null, no clients are selected. 1644 * 1645 * @see IUiSelectionListener 1646 */ selectionChanged(IDevice selectedDevice, Client selectedClient)1647 public void selectionChanged(IDevice selectedDevice, Client selectedClient) { 1648 if (mCurrentDevice != selectedDevice) { 1649 mCurrentDevice = selectedDevice; 1650 for (TablePanel panel : mPanels) { 1651 if (panel != null) { 1652 panel.deviceSelected(mCurrentDevice); 1653 } 1654 } 1655 1656 mEmulatorPanel.deviceSelected(mCurrentDevice); 1657 mLogPanel.deviceSelected(mCurrentDevice); 1658 if (mEventLogPanel != null) { 1659 mEventLogPanel.deviceSelected(mCurrentDevice); 1660 } 1661 1662 if (mExplorer != null) { 1663 mExplorer.switchDevice(mCurrentDevice); 1664 } 1665 } 1666 1667 if (mCurrentClient != selectedClient) { 1668 AndroidDebugBridge.getBridge().setSelectedClient(selectedClient); 1669 mCurrentClient = selectedClient; 1670 for (TablePanel panel : mPanels) { 1671 if (panel != null) { 1672 panel.clientSelected(mCurrentClient); 1673 } 1674 } 1675 1676 enableButtons(); 1677 } 1678 } 1679 clientChanged(Client client, int changeMask)1680 public void clientChanged(Client client, int changeMask) { 1681 if ((changeMask & Client.CHANGE_METHOD_PROFILING_STATUS) == 1682 Client.CHANGE_METHOD_PROFILING_STATUS) { 1683 if (mCurrentClient == client) { 1684 mDisplay.asyncExec(new Runnable() { 1685 public void run() { 1686 // force refresh of the button enabled state. 1687 enableButtons(); 1688 } 1689 }); 1690 } 1691 } 1692 } 1693 } 1694