From 2ecd3a82201d03a35ab124f466941dce4d4be637 Mon Sep 17 00:00:00 2001 From: Siva Velusamy Date: Wed, 3 Aug 2011 11:19:33 -0700 Subject: Initial implementation of the new logcat panel. This logcat panel will show up in ddms if it is launched with -Dcom.android.ddms.useNewLogCatView. Currently, this implementation only displays a table with a list of logcat messages. It lacks features such as filters, go-to source of exception, export to log, user preferences, etc. Future patches should enhance the UI to achieve feature parity with older UI. Change-Id: I3dde3c590c839318ce57bb5f005627f580ebb06c --- ddms/app/src/com/android/ddms/UIThread.java | 16 ++ .../logcat/ILogCatMessageEventListener.java | 25 +++ .../com/android/ddmuilib/logcat/LogCatMessage.java | 73 ++++++++ .../logcat/LogCatMessageContentProvider.java | 40 +++++ .../logcat/LogCatMessageLabelProvider.java | 159 ++++++++++++++++++ .../android/ddmuilib/logcat/LogCatMessageList.java | 70 ++++++++ .../ddmuilib/logcat/LogCatMessageParser.java | 89 ++++++++++ .../com/android/ddmuilib/logcat/LogCatPanel.java | 146 ++++++++++++++++ .../android/ddmuilib/logcat/LogCatReceiver.java | 183 +++++++++++++++++++++ 9 files changed, 801 insertions(+) create mode 100644 ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatMessageEventListener.java create mode 100644 ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessage.java create mode 100644 ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageContentProvider.java create mode 100644 ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageLabelProvider.java create mode 100644 ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java create mode 100644 ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageParser.java create mode 100644 ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java create mode 100644 ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java (limited to 'ddms') diff --git a/ddms/app/src/com/android/ddms/UIThread.java b/ddms/app/src/com/android/ddms/UIThread.java index 9931a93..c9dc2b1 100644 --- a/ddms/app/src/com/android/ddms/UIThread.java +++ b/ddms/app/src/com/android/ddms/UIThread.java @@ -46,6 +46,8 @@ import com.android.ddmuilib.explorer.DeviceExplorer; import com.android.ddmuilib.handler.BaseFileHandler; import com.android.ddmuilib.handler.MethodProfilingHandler; import com.android.ddmuilib.log.event.EventLogPanel; +import com.android.ddmuilib.logcat.LogCatPanel; +import com.android.ddmuilib.logcat.LogCatReceiver; import com.android.ddmuilib.logcat.LogColors; import com.android.ddmuilib.logcat.LogFilter; import com.android.ddmuilib.logcat.LogPanel; @@ -232,6 +234,7 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { } private LogPanel mLogPanel; /* only valid when useOldLogCatView() == true */ + private LogCatPanel mLogCatPanel; /* only valid when useOldLogCatView() == false */ private ToolItemAction mCreateFilterAction; private ToolItemAction mDeleteFilterAction; @@ -959,6 +962,8 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { if (useOldLogCatView()) { createBottomPanel(bottomPanel); + } else { + createLogCatView(bottomPanel); } // form layout data @@ -1359,6 +1364,15 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { mLogPanel.startLogCat(mCurrentDevice); } + private void createLogCatView(Composite parent) { + mLogCatPanel = new LogCatPanel(new LogCatReceiver()); + mLogCatPanel.createPanel(parent); + + if (mCurrentDevice != null) { + mLogCatPanel.deviceSelected(mCurrentDevice); + } + } + /* * Create the contents of the left panel: a table of VMs. */ @@ -1701,6 +1715,8 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { mEmulatorPanel.deviceSelected(mCurrentDevice); if (useOldLogCatView()) { mLogPanel.deviceSelected(mCurrentDevice); + } else { + mLogCatPanel.deviceSelected(mCurrentDevice); } if (mEventLogPanel != null) { mEventLogPanel.deviceSelected(mCurrentDevice); diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatMessageEventListener.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatMessageEventListener.java new file mode 100644 index 0000000..4ef8b51 --- /dev/null +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatMessageEventListener.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ddmuilib.logcat; + +/** + * Listeners interested in log cat messages should implement this interface. + */ +public interface ILogCatMessageEventListener { + /** Called on reception of logcat messages. */ + void messageReceived(); +} diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessage.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessage.java new file mode 100644 index 0000000..e542ead --- /dev/null +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessage.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ddmuilib.logcat; + +import com.android.ddmlib.Log.LogLevel; + +/** + * Model a single log message output from {@code logcat -v long}. + * A logcat message has a {@link LogLevel}, the pid (process id) of the process + * generating the message, the time at which the message was generated, and + * the tag and message itself. + */ +public final class LogCatMessage { + private final LogLevel mLogLevel; + private final String mPidString; + private final String mTag; + private final String mTime; + private final String mMessage; + + /** + * Construct an immutable log message object. + */ + public LogCatMessage(LogLevel logLevel, String pid, String tag, String time, String msg) { + mLogLevel = logLevel; + mPidString = pid; + mTag = tag; + mTime = time; + mMessage = msg; + } + + public LogLevel getLogLevel() { + return mLogLevel; + } + + public String getPidString() { + return mPidString; + } + + public String getTag() { + return mTag; + } + + public String getTime() { + return mTime; + } + + public String getMessage() { + return mMessage; + } + + @Override + public String toString() { + return mTime + ": " + + mLogLevel.getPriorityLetter() + "/" + + mTag + "(" + + mPidString + "): " + + mMessage; + } +} diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageContentProvider.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageContentProvider.java new file mode 100644 index 0000000..27b0456 --- /dev/null +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageContentProvider.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ddmuilib.logcat; + +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.Viewer; + +/** + * A JFace content provider for the LogCat log messages, used in the {@link LogCatPanel}. + */ +public final class LogCatMessageContentProvider implements IStructuredContentProvider { + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + public Object[] getElements(Object model) { + if (model instanceof LogCatMessageList) { + Object[] e = ((LogCatMessageList) model).toArray(); + return e; + } + + return null; + } +} diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageLabelProvider.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageLabelProvider.java new file mode 100644 index 0000000..42c8e00 --- /dev/null +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageLabelProvider.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ddmuilib.logcat; + +import com.android.ddmlib.Log.LogLevel; + +import org.eclipse.jface.viewers.ITableColorProvider; +import org.eclipse.jface.viewers.ITableFontProvider; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Display; + +/** + * A JFace label provider for the LogCat log messages. It expects elements of type + * {@link LogCatMessage}. + */ +public final class LogCatMessageLabelProvider extends LabelProvider + implements ITableLabelProvider, ITableColorProvider, ITableFontProvider { + /* Default Colors for different log levels. */ + private static final Color INFO_MSG_COLOR = new Color(null, 0, 127, 0); + private static final Color DEBUG_MSG_COLOR = new Color(null, 0, 0, 127); + private static final Color ERROR_MSG_COLOR = new Color(null, 255, 0, 0); + private static final Color WARN_MSG_COLOR = new Color(null, 255, 127, 0); + private static final Color VERBOSE_MSG_COLOR = new Color(null, 0, 0, 0); + + private int mWrapWidth; + private Font mLogFont; + + /** + * Construct a label provider that will wrap lines of length > wrapWidth. + * @param wrapWidth width at which to wrap lines + */ + public LogCatMessageLabelProvider(int wrapWidth) { + mWrapWidth = wrapWidth; + } + + public Image getColumnImage(Object element, int index) { + return null; + } + + /** + * Obtain the correct text for the given column index. The index ordering + * should match the ordering of the columns in the table created in {@link LogCatPanel}. + * @return text to be used for specific column index + */ + public String getColumnText(Object element, int index) { + if (!(element instanceof LogCatMessage)) { + return null; + } + + LogCatMessage m = (LogCatMessage) element; + switch (index) { + case 0: + return Character.toString(m.getLogLevel().getPriorityLetter()); + case 1: + return m.getTime(); + case 2: + return m.getPidString(); + case 3: + return m.getTag(); + case 4: + String msg = m.getMessage(); + if (msg.length() < mWrapWidth) { + return msg; + } + return wrapMessage(msg, mWrapWidth); + default: + return null; + } + } + + /** + * Wrap a string into multiple lines if it exceeds a certain width. + * @param msg message string to line wrap + * @param width width at which to wrap + * @return line with newline's inserted + */ + private String wrapMessage(String msg, int width) { + StringBuffer sb = new StringBuffer(); + + int offset = 0; + int len = msg.length(); + + while (len > 0) { + if (offset != 0) { + /* for all lines but the first one, add a newline and + * two spaces at the beginning. */ + sb.append("\n "); + } + + int copylen = Math.min(width, len); + sb.append(msg.substring(offset, offset + copylen)); + + offset += copylen; + len -= copylen; + } + + return sb.toString(); + } + + public Color getBackground(Object element, int index) { + return null; + } + + /** + * Get the foreground text color for given table item and column. The color + * depends only on the log level, and the same color is used in all columns. + */ + public Color getForeground(Object element, int index) { + if (!(element instanceof LogCatMessage)) { + return null; + } + + LogCatMessage m = (LogCatMessage) element; + LogLevel l = m.getLogLevel(); + + if (l.equals(LogLevel.VERBOSE)) { + return VERBOSE_MSG_COLOR; + } else if (l.equals(LogLevel.INFO)) { + return INFO_MSG_COLOR; + } else if (l.equals(LogLevel.DEBUG)) { + return DEBUG_MSG_COLOR; + } else if (l.equals(LogLevel.ERROR)) { + return ERROR_MSG_COLOR; + } else if (l.equals(LogLevel.WARN)) { + return WARN_MSG_COLOR; + } + + return null; + } + + public Font getFont(Object element, int index) { + if (mLogFont == null) { + /* FIXME: this should be obtained from preference settings. */ + mLogFont = new Font(Display.getDefault(), + new FontData("Courier New", 10, SWT.NORMAL)); + } + return mLogFont; + } +} diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java new file mode 100644 index 0000000..6adca85 --- /dev/null +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ddmuilib.logcat; + +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; + +/** + * Container for a list of log messages. The list of messages are + * maintained in a circular buffer (FIFO). + */ +public final class LogCatMessageList { + /** Size of the FIFO. + * FIXME: this should be a user preference. + */ + private static final int MAX_MESSAGES = 1000; + + private Queue mQ; + private LogCatMessage[] mQArray; + + /** + * Construct an empty message list. + */ + public LogCatMessageList() { + mQ = new ArrayBlockingQueue(MAX_MESSAGES); + mQArray = new LogCatMessage[MAX_MESSAGES]; + } + + /** + * Append a message to the list. If the list is full, the first + * message will be popped off of it. + * @param m log to be inserted + */ + public synchronized void appendMessage(final LogCatMessage m) { + if (mQ.size() == MAX_MESSAGES) { + /* make space by removing the first entry */ + mQ.poll(); + } + mQ.offer(m); + } + + /** + * Obtain all the messages currently present in the list. + * @return array containing all the log messages + */ + public Object[] toArray() { + if (mQ.size() == MAX_MESSAGES) { + /* + * Once the queue is full, it stays full until the user explicitly clears + * all the logs. Optimize for this case by not reallocating the array. + */ + return mQ.toArray(mQArray); + } + return mQ.toArray(); + } +} diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageParser.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageParser.java new file mode 100644 index 0000000..cc90db9 --- /dev/null +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageParser.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ddmuilib.logcat; + +import com.android.ddmlib.Log.LogLevel; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Class to parse raw output of {@code adb logcat -v long} to {@link LogCatMessage} objects. + */ +public final class LogCatMessageParser { + private LogLevel mCurLogLevel = LogLevel.WARN; + private String mCurPid = "?"; + private String mCurTag = "?"; + private String mCurTime = "?:??"; + + /** + * This pattern is meant to parse the first line of a log message with the option + * 'logcat -v long'. The first line represents the date, tag, severity, etc.. while the + * following lines are the message (can be several lines).
+ * This first line looks something like:
+ * {@code "[ 00-00 00:00:00.000 :0x /]"} + *
+ * Note: severity is one of V, D, I, W, E or A
+ * Note: the fraction of second value can have any number of digit.
+ * Note: the tag should be trimmed as it may have spaces at the end. + */ + private static Pattern sLogHeaderPattern = Pattern.compile( + "^\\[\\s(\\d\\d-\\d\\d\\s\\d\\d:\\d\\d:\\d\\d\\.\\d+)" + + "\\s+(\\d*):(0x[0-9a-fA-F]+)\\s([VDIWEA])/(.*)\\]$"); + + /** + * Parse a list of strings into {@link LogCatMessage} objects. This method + * maintains state from previous calls regarding the last seen header of + * logcat messages. + * @param lines list of raw strings obtained from logcat -v long + * @return list of LogMessage objects parsed from the input + */ + public List processLogLines(String[] lines) { + List messages = new ArrayList(lines.length); + + for (String line : lines) { + if (line.length() == 0) { + continue; + } + + Matcher matcher = sLogHeaderPattern.matcher(line); + if (matcher.matches()) { + mCurTime = matcher.group(1); + mCurPid = matcher.group(2); + mCurLogLevel = LogLevel.getByLetterString(matcher.group(4)); + mCurTag = matcher.group(5).trim(); + } else { + LogCatMessage m = new LogCatMessage(mCurLogLevel, mCurPid, mCurTag, mCurTime, line); + messages.add(m); + } + } + + return messages; + } + + /** + * Reset any header state information stored from previous calls to the parser. + */ + public void resetState() { + mCurLogLevel = LogLevel.WARN; + mCurPid = "?"; + mCurTag = "?"; + mCurTime = "?:??"; + } +} diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java new file mode 100644 index 0000000..9b35159 --- /dev/null +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ddmuilib.logcat; + +import com.android.ddmuilib.SelectionDependentPanel; + +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; + +/** + * LogCatPanel displays a table listing the logcat messages. + */ +public final class LogCatPanel extends SelectionDependentPanel + implements ILogCatMessageEventListener { + /** Width (in characters) at which to wrap messages. SWT Tables do not + * auto wrap long text - they simply clip the text. + * FIXME: this should be a preference. */ + private static final int MSG_WRAP_WIDTH = 150; + + private TableViewer mViewer; + private LogCatReceiver mReceiver; + + /** + * Construct a logcat panel. + * @param r source of logcat messages. + */ + public LogCatPanel(LogCatReceiver r) { + mReceiver = r; + mReceiver.addMessageReceivedEventListener(this); + } + + @Override + public void deviceSelected() { + mReceiver.stop(); + mReceiver.start(getCurrentDevice()); + mViewer.setInput(mReceiver.getMessages()); + } + + @Override + public void clientSelected() { + } + + @Override + protected void postCreation() { + } + + @Override + protected Control createControl(Composite parent) { + GridLayout layout = new GridLayout(2, false); + parent.setLayout(layout); + + createLogcatViewTable(parent); + + return null; + } + + private void createLogcatViewTable(Composite parent) { + /* SWT.VIRTUAL style will make the table render faster, but all rows will be + * of equal heights which causes wrapped messages to just be clipped. */ + final Table table = new Table(parent, SWT.FULL_SELECTION); + mViewer = new TableViewer(table); + + GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true); + gd.horizontalSpan = 2; + mViewer.getTable().setLayoutData(gd); + table.getHorizontalBar().setVisible(true); + + /** Fields to show in the table. */ + String []properties = { + "Level", + "Time", + "PID", + "Tag", + "Text", + }; + + /** Column widths (in px) corresponding to the above fields. */ + int []colWidths = { + 50, + 150, + 50, + 200, + 1000, + }; + + for (int i = 0; i < properties.length; i++) { + TableColumn col = new TableColumn(mViewer.getTable(), SWT.NONE, i); + col.setWidth(colWidths[i]); + col.setText(properties[i]); + } + + mViewer.getTable().setLinesVisible(true); /* zebra stripe the table */ + mViewer.getTable().setHeaderVisible(true); + + mViewer.setLabelProvider(new LogCatMessageLabelProvider(MSG_WRAP_WIDTH)); + mViewer.setContentProvider(new LogCatMessageContentProvider()); + mViewer.setInput(mReceiver.getMessages()); + } + + @Override + public void setFocus() { + } + + /** + * Update view whenever a message is received. + * Implements {@link ILogCatMessageEventListener#messageReceived()}. + */ + public void messageReceived() { + Display.getDefault().asyncExec(new Runnable() { + public void run() { + if (mViewer.getTable().isDisposed()) { + return; + } + mViewer.refresh(); + + /* if an item has been selected, then don't scroll the table, + * otherwise, always display the latest output. + * FIXME: this behavior should be controlled via a "scroll lock" button in UI. */ + if (mViewer.getTable().getSelectionCount() == 0) { + mViewer.getTable().setTopIndex(mViewer.getTable().getItemCount() - 1); + } + } + }); + } +} diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java new file mode 100644 index 0000000..9c737f9 --- /dev/null +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ddmuilib.logcat; + +import com.android.ddmlib.IDevice; +import com.android.ddmlib.Log; +import com.android.ddmlib.MultiLineReceiver; + +import java.util.ArrayList; +import java.util.List; + +/** + * A class to monitor a device for logcat messages. It stores the received + * log messages in a circular buffer. + */ +public final class LogCatReceiver { + private static final String LOGCAT_COMMAND = "logcat -v long"; + private static final int DEVICE_POLL_INTERVAL_MSEC = 1000; + + private LogCatMessageList mLogMessages; + private IDevice mCurrentDevice; + private LogCatOutputReceiver mCurrentLogCatOutputReceiver; + private List mLogCatMessageListeners; + private LogCatMessageParser mLogCatMessageParser; + + /** + * Construct a LogCat message receiver. + */ + public LogCatReceiver() { + mLogCatMessageListeners = new ArrayList(); + mLogCatMessageParser = new LogCatMessageParser(); + } + + /** + * Stop receiving messages from currently active device. + */ + public void stop() { + if (mCurrentLogCatOutputReceiver != null) { + /* stop the current logcat command */ + mCurrentLogCatOutputReceiver.mIsCancelled = true; + mCurrentLogCatOutputReceiver = null; + } + + mLogMessages = null; + mCurrentDevice = null; + } + + /** + * Start monitoring a device for logcat messages. This will launch a + * logcat command on the device, and monitor the output of that command in + * a separate thread. All logcat messages are then stored in a circular + * buffer, which can be retrieved using {@link LogCatReceiver#getMessages()}. + * @param device device to monitor for logcat messages + */ + public void start(IDevice device) { + if (device == null) { + return; + } + + if (mCurrentDevice == device) { + return; + } + + /* stop currently active listeners before starting a new one. */ + if (mCurrentDevice != null) { + stop(); + } + + mCurrentDevice = device; + mLogMessages = new LogCatMessageList(); + + mLogCatMessageParser.resetState(); + startReceiverThread(); + } + + private void startReceiverThread() { + mCurrentLogCatOutputReceiver = new LogCatOutputReceiver(); + + Thread t = new Thread(new Runnable() { + public void run() { + /* wait while the device comes online */ + while (!mCurrentDevice.isOnline()) { + try { + Thread.sleep(DEVICE_POLL_INTERVAL_MSEC); + } catch (InterruptedException e) { + return; + } + } + + try { + mCurrentDevice.executeShellCommand(LOGCAT_COMMAND, + mCurrentLogCatOutputReceiver, 0); + } catch (Exception e) { + /* There are 4 possible exceptions: TimeoutException, + * AdbCommandRejectedException, ShellCommandUnresponsiveException and + * IOException. In case of any of them, the only recourse is to just + * log this unexpected situation and move on. + */ + Log.e("Unexpected error while launching logcat. Try reselecting the device.", + e); + } + } + }); + t.setName("LogCat output receiver for " + mCurrentDevice.getSerialNumber()); + t.start(); + } + + /** + * LogCatOutputReceiver implements {@link MultiLineReceiver#processNewLines(String[])}, + * which is called whenever there is output from logcat. It simply redirects this output + * to {@link LogCatReceiver#processLogLines(String[])}. This class is expected to be + * used from a different thread, and the only way to stop that thread is by using the + * {@link LogCatOutputReceiver#mIsCancelled} variable. + * See {@link IDevice#executeShellCommand(String, IShellOutputReceiver, int)} for more + * details. + */ + private class LogCatOutputReceiver extends MultiLineReceiver { + private boolean mIsCancelled; + + public LogCatOutputReceiver() { + setTrimLine(false); + } + + /** Implements {@link IShellOutputReceiver#isCancelled() }. */ + public boolean isCancelled() { + return mIsCancelled; + } + + @Override + public void processNewLines(String[] lines) { + if (!mIsCancelled) { + processLogLines(lines); + } + } + } + + private void processLogLines(String[] lines) { + List messages = mLogCatMessageParser.processLogLines(lines); + + if (messages.size() > 0) { + for (LogCatMessage m: messages) { + mLogMessages.appendMessage(m); + } + sendMessageReceivedEvent(); + } + } + + /** + * Get the list of logcat messages received from currently active device. + * @return list of messages if currently listening, null otherwise + */ + public LogCatMessageList getMessages() { + return mLogMessages; + } + + /** + * Add to list of message event listeners. + * @param l listener to notified when messages are received from the device + */ + public void addMessageReceivedEventListener(ILogCatMessageEventListener l) { + mLogCatMessageListeners.add(l); + } + + private void sendMessageReceivedEvent() { + for (ILogCatMessageEventListener l: mLogCatMessageListeners) { + l.messageReceived(); + } + } +} -- cgit v1.1