aboutsummaryrefslogtreecommitdiffstats
path: root/ddms
diff options
context:
space:
mode:
authorSiva Velusamy <vsiva@google.com>2011-08-03 11:19:33 -0700
committerSiva Velusamy <vsiva@google.com>2011-08-04 11:59:18 -0700
commit2ecd3a82201d03a35ab124f466941dce4d4be637 (patch)
tree95d678af931b07bf45b46140f909b60b26b0361f /ddms
parentc4c3f16bedc1049d1888d34d27de13b3f6cce55a (diff)
downloadsdk-2ecd3a82201d03a35ab124f466941dce4d4be637.zip
sdk-2ecd3a82201d03a35ab124f466941dce4d4be637.tar.gz
sdk-2ecd3a82201d03a35ab124f466941dce4d4be637.tar.bz2
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
Diffstat (limited to 'ddms')
-rw-r--r--ddms/app/src/com/android/ddms/UIThread.java16
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatMessageEventListener.java25
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessage.java73
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageContentProvider.java40
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageLabelProvider.java159
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java70
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageParser.java89
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java146
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java183
9 files changed, 801 insertions, 0 deletions
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<LogCatMessage> mQ;
+ private LogCatMessage[] mQArray;
+
+ /**
+ * Construct an empty message list.
+ */
+ public LogCatMessageList() {
+ mQ = new ArrayBlockingQueue<LogCatMessage>(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).<br>
+ * This first line looks something like:<br>
+ * {@code "[ 00-00 00:00:00.000 <pid>:0x<???> <severity>/<tag>]"}
+ * <br>
+ * Note: severity is one of V, D, I, W, E or A<br>
+ * Note: the fraction of second value can have any number of digit.<br>
+ * 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<LogCatMessage> processLogLines(String[] lines) {
+ List<LogCatMessage> messages = new ArrayList<LogCatMessage>(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<ILogCatMessageEventListener> mLogCatMessageListeners;
+ private LogCatMessageParser mLogCatMessageParser;
+
+ /**
+ * Construct a LogCat message receiver.
+ */
+ public LogCatReceiver() {
+ mLogCatMessageListeners = new ArrayList<ILogCatMessageEventListener>();
+ 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<LogCatMessage> 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();
+ }
+ }
+}