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