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