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