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