| /* |
| * Copyright (C) 2007 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; |
| import com.android.ddmlib.Log.LogLevel; |
| import com.android.ddmuilib.annotation.UiThread; |
| import com.android.ddmuilib.logcat.LogPanel.LogMessage; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.SWTException; |
| import org.eclipse.swt.widgets.ScrollBar; |
| import org.eclipse.swt.widgets.TabItem; |
| import org.eclipse.swt.widgets.Table; |
| import org.eclipse.swt.widgets.TableItem; |
| |
| import java.util.ArrayList; |
| import java.util.regex.PatternSyntaxException; |
| |
| /** logcat output filter class */ |
| public class LogFilter { |
| |
| public final static int MODE_PID = 0x01; |
| public final static int MODE_TAG = 0x02; |
| public final static int MODE_LEVEL = 0x04; |
| |
| private String mName; |
| |
| /** |
| * Filtering mode. Value can be a mix of MODE_PID, MODE_TAG, MODE_LEVEL |
| */ |
| private int mMode = 0; |
| |
| /** |
| * pid used for filtering. Only valid if mMode is MODE_PID. |
| */ |
| private int mPid; |
| |
| /** Single level log level as defined in Log.mLevelChar. Only valid |
| * if mMode is MODE_LEVEL */ |
| private int mLogLevel; |
| |
| /** |
| * log tag filtering. Only valid if mMode is MODE_TAG |
| */ |
| private String mTag; |
| |
| private Table mTable; |
| private TabItem mTabItem; |
| private boolean mIsCurrentTabItem = false; |
| private int mUnreadCount = 0; |
| |
| /** Temp keyword filtering */ |
| private String[] mTempKeywordFilters; |
| |
| /** temp pid filtering */ |
| private int mTempPid = -1; |
| |
| /** temp tag filtering */ |
| private String mTempTag; |
| |
| /** temp log level filtering */ |
| private int mTempLogLevel = -1; |
| |
| private LogColors mColors; |
| |
| private boolean mTempFilteringStatus = false; |
| |
| private final ArrayList<LogMessage> mMessages = new ArrayList<LogMessage>(); |
| private final ArrayList<LogMessage> mNewMessages = new ArrayList<LogMessage>(); |
| |
| private boolean mSupportsDelete = true; |
| private boolean mSupportsEdit = true; |
| private int mRemovedMessageCount = 0; |
| |
| /** |
| * Creates a filter with a particular mode. |
| * @param name The name to be displayed in the UI |
| */ |
| public LogFilter(String name) { |
| mName = name; |
| } |
| |
| public LogFilter() { |
| |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(mName); |
| |
| sb.append(':'); |
| sb.append(mMode); |
| if ((mMode & MODE_PID) == MODE_PID) { |
| sb.append(':'); |
| sb.append(mPid); |
| } |
| |
| if ((mMode & MODE_LEVEL) == MODE_LEVEL) { |
| sb.append(':'); |
| sb.append(mLogLevel); |
| } |
| |
| if ((mMode & MODE_TAG) == MODE_TAG) { |
| sb.append(':'); |
| sb.append(mTag); |
| } |
| |
| return sb.toString(); |
| } |
| |
| public boolean loadFromString(String string) { |
| String[] segments = string.split(":"); // $NON-NLS-1$ |
| int index = 0; |
| |
| // get the name |
| mName = segments[index++]; |
| |
| // get the mode |
| mMode = Integer.parseInt(segments[index++]); |
| |
| if ((mMode & MODE_PID) == MODE_PID) { |
| mPid = Integer.parseInt(segments[index++]); |
| } |
| |
| if ((mMode & MODE_LEVEL) == MODE_LEVEL) { |
| mLogLevel = Integer.parseInt(segments[index++]); |
| } |
| |
| if ((mMode & MODE_TAG) == MODE_TAG) { |
| mTag = segments[index++]; |
| } |
| |
| return true; |
| } |
| |
| |
| /** Sets the name of the filter. */ |
| void setName(String name) { |
| mName = name; |
| } |
| |
| /** |
| * Returns the UI display name. |
| */ |
| public String getName() { |
| return mName; |
| } |
| |
| /** |
| * Set the Table ui widget associated with this filter. |
| * @param tabItem The item in the TabFolder |
| * @param table The Table object |
| */ |
| public void setWidgets(TabItem tabItem, Table table) { |
| mTable = table; |
| mTabItem = tabItem; |
| } |
| |
| /** |
| * Returns true if the filter is ready for ui. |
| */ |
| public boolean uiReady() { |
| return (mTable != null && mTabItem != null); |
| } |
| |
| /** |
| * Returns the UI table object. |
| * @return |
| */ |
| public Table getTable() { |
| return mTable; |
| } |
| |
| public void dispose() { |
| mTable.dispose(); |
| mTabItem.dispose(); |
| mTable = null; |
| mTabItem = null; |
| } |
| |
| /** |
| * Resets the filtering mode to be 0 (i.e. no filter). |
| */ |
| public void resetFilteringMode() { |
| mMode = 0; |
| } |
| |
| /** |
| * Returns the current filtering mode. |
| * @return A bitmask. Possible values are MODE_PID, MODE_TAG, MODE_LEVEL |
| */ |
| public int getFilteringMode() { |
| return mMode; |
| } |
| |
| /** |
| * Adds PID to the current filtering mode. |
| * @param pid |
| */ |
| public void setPidMode(int pid) { |
| if (pid != -1) { |
| mMode |= MODE_PID; |
| } else { |
| mMode &= ~MODE_PID; |
| } |
| mPid = pid; |
| } |
| |
| /** Returns the pid filter if valid, otherwise -1 */ |
| public int getPidFilter() { |
| if ((mMode & MODE_PID) == MODE_PID) |
| return mPid; |
| return -1; |
| } |
| |
| public void setTagMode(String tag) { |
| if (tag != null && tag.length() > 0) { |
| mMode |= MODE_TAG; |
| } else { |
| mMode &= ~MODE_TAG; |
| } |
| mTag = tag; |
| } |
| |
| public String getTagFilter() { |
| if ((mMode & MODE_TAG) == MODE_TAG) |
| return mTag; |
| return null; |
| } |
| |
| public void setLogLevel(int level) { |
| if (level == -1) { |
| mMode &= ~MODE_LEVEL; |
| } else { |
| mMode |= MODE_LEVEL; |
| mLogLevel = level; |
| } |
| |
| } |
| |
| public int getLogLevel() { |
| if ((mMode & MODE_LEVEL) == MODE_LEVEL) { |
| return mLogLevel; |
| } |
| |
| return -1; |
| } |
| |
| |
| public boolean supportsDelete() { |
| return mSupportsDelete ; |
| } |
| |
| public boolean supportsEdit() { |
| return mSupportsEdit; |
| } |
| |
| /** |
| * Sets the selected state of the filter. |
| * @param selected selection state. |
| */ |
| public void setSelectedState(boolean selected) { |
| if (selected) { |
| if (mTabItem != null) { |
| mTabItem.setText(mName); |
| } |
| mUnreadCount = 0; |
| } |
| mIsCurrentTabItem = selected; |
| } |
| |
| /** |
| * Adds a new message and optionally removes an old message. |
| * <p/>The new message is filtered through {@link #accept(LogMessage)}. |
| * Calls to {@link #flush()} from a UI thread will display it (and other |
| * pending messages) to the associated {@link Table}. |
| * @param logMessage the MessageData object to filter |
| * @return true if the message was accepted. |
| */ |
| public boolean addMessage(LogMessage newMessage, LogMessage oldMessage) { |
| synchronized (mMessages) { |
| if (oldMessage != null) { |
| int index = mMessages.indexOf(oldMessage); |
| if (index != -1) { |
| // TODO check that index will always be -1 or 0, as only the oldest message is ever removed. |
| mMessages.remove(index); |
| mRemovedMessageCount++; |
| } |
| |
| // now we look for it in mNewMessages. This can happen if the new message is added |
| // and then removed because too many messages are added between calls to #flush() |
| index = mNewMessages.indexOf(oldMessage); |
| if (index != -1) { |
| // TODO check that index will always be -1 or 0, as only the oldest message is ever removed. |
| mNewMessages.remove(index); |
| } |
| } |
| |
| boolean filter = accept(newMessage); |
| |
| if (filter) { |
| // at this point the message is accepted, we add it to the list |
| mMessages.add(newMessage); |
| mNewMessages.add(newMessage); |
| } |
| |
| return filter; |
| } |
| } |
| |
| /** |
| * Removes all the items in the filter and its {@link Table}. |
| */ |
| public void clear() { |
| mRemovedMessageCount = 0; |
| mNewMessages.clear(); |
| mMessages.clear(); |
| mTable.removeAll(); |
| } |
| |
| /** |
| * Filters a message. |
| * @param logMessage the Message |
| * @return true if the message is accepted by the filter. |
| */ |
| boolean accept(LogMessage logMessage) { |
| // do the regular filtering now |
| if ((mMode & MODE_PID) == MODE_PID && mPid != logMessage.data.pid) { |
| return false; |
| } |
| |
| if ((mMode & MODE_TAG) == MODE_TAG && ( |
| logMessage.data.tag == null || |
| logMessage.data.tag.equals(mTag) == false)) { |
| return false; |
| } |
| |
| int msgLogLevel = logMessage.data.logLevel.getPriority(); |
| |
| // test the temp log filtering first, as it replaces the old one |
| if (mTempLogLevel != -1) { |
| if (mTempLogLevel > msgLogLevel) { |
| return false; |
| } |
| } else if ((mMode & MODE_LEVEL) == MODE_LEVEL && |
| mLogLevel > msgLogLevel) { |
| return false; |
| } |
| |
| // do the temp filtering now. |
| if (mTempKeywordFilters != null) { |
| String msg = logMessage.msg; |
| |
| for (String kw : mTempKeywordFilters) { |
| try { |
| if (msg.contains(kw) == false && msg.matches(kw) == false) { |
| return false; |
| } |
| } catch (PatternSyntaxException e) { |
| // if the string is not a valid regular expression, |
| // this exception is thrown. |
| return false; |
| } |
| } |
| } |
| |
| if (mTempPid != -1 && mTempPid != logMessage.data.pid) { |
| return false; |
| } |
| |
| if (mTempTag != null && mTempTag.length() > 0) { |
| if (mTempTag.equals(logMessage.data.tag) == false) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Takes all the accepted messages and display them. |
| * This must be called from a UI thread. |
| */ |
| @UiThread |
| public void flush() { |
| // if scroll bar is at the bottom, we will scroll |
| ScrollBar bar = mTable.getVerticalBar(); |
| boolean scroll = bar.getMaximum() == bar.getSelection() + bar.getThumb(); |
| |
| // if we are not going to scroll, get the current first item being shown. |
| int topIndex = mTable.getTopIndex(); |
| |
| // disable drawing |
| mTable.setRedraw(false); |
| |
| int totalCount = mNewMessages.size(); |
| |
| try { |
| // remove the items of the old messages. |
| for (int i = 0 ; i < mRemovedMessageCount && mTable.getItemCount() > 0 ; i++) { |
| mTable.remove(0); |
| } |
| |
| if (mUnreadCount > mTable.getItemCount()) { |
| mUnreadCount = mTable.getItemCount(); |
| } |
| |
| // add the new items |
| for (int i = 0 ; i < totalCount ; i++) { |
| LogMessage msg = mNewMessages.get(i); |
| addTableItem(msg); |
| } |
| } catch (SWTException e) { |
| // log the error and keep going. Content of the logcat table maybe unexpected |
| // but at least ddms won't crash. |
| Log.e("LogFilter", e); |
| } |
| |
| // redraw |
| mTable.setRedraw(true); |
| |
| // scroll if needed, by showing the last item |
| if (scroll) { |
| totalCount = mTable.getItemCount(); |
| if (totalCount > 0) { |
| mTable.showItem(mTable.getItem(totalCount-1)); |
| } |
| } else if (mRemovedMessageCount > 0) { |
| // we need to make sure the topIndex is still visible. |
| // Because really old items are removed from the list, this could make it disappear |
| // if we don't change the scroll value at all. |
| |
| topIndex -= mRemovedMessageCount; |
| if (topIndex < 0) { |
| // looks like it disappeared. Lets just show the first item |
| mTable.showItem(mTable.getItem(0)); |
| } else { |
| mTable.showItem(mTable.getItem(topIndex)); |
| } |
| } |
| |
| // if this filter is not the current one, we update the tab text |
| // with the amount of unread message |
| if (mIsCurrentTabItem == false) { |
| mUnreadCount += mNewMessages.size(); |
| totalCount = mTable.getItemCount(); |
| if (mUnreadCount > 0) { |
| mTabItem.setText(mName + " (" // $NON-NLS-1$ |
| + (mUnreadCount > totalCount ? totalCount : mUnreadCount) |
| + ")"); // $NON-NLS-1$ |
| } else { |
| mTabItem.setText(mName); // $NON-NLS-1$ |
| } |
| } |
| |
| mNewMessages.clear(); |
| } |
| |
| void setColors(LogColors colors) { |
| mColors = colors; |
| } |
| |
| int getUnreadCount() { |
| return mUnreadCount; |
| } |
| |
| void setUnreadCount(int unreadCount) { |
| mUnreadCount = unreadCount; |
| } |
| |
| void setSupportsDelete(boolean support) { |
| mSupportsDelete = support; |
| } |
| |
| void setSupportsEdit(boolean support) { |
| mSupportsEdit = support; |
| } |
| |
| void setTempKeywordFiltering(String[] segments) { |
| mTempKeywordFilters = segments; |
| mTempFilteringStatus = true; |
| } |
| |
| void setTempPidFiltering(int pid) { |
| mTempPid = pid; |
| mTempFilteringStatus = true; |
| } |
| |
| void setTempTagFiltering(String tag) { |
| mTempTag = tag; |
| mTempFilteringStatus = true; |
| } |
| |
| void resetTempFiltering() { |
| if (mTempPid != -1 || mTempTag != null || mTempKeywordFilters != null) { |
| mTempFilteringStatus = true; |
| } |
| |
| mTempPid = -1; |
| mTempTag = null; |
| mTempKeywordFilters = null; |
| } |
| |
| void resetTempFilteringStatus() { |
| mTempFilteringStatus = false; |
| } |
| |
| boolean getTempFilterStatus() { |
| return mTempFilteringStatus; |
| } |
| |
| |
| /** |
| * Add a TableItem for the index-th item of the buffer |
| * @param filter The index of the table in which to insert the item. |
| */ |
| private void addTableItem(LogMessage msg) { |
| TableItem item = new TableItem(mTable, SWT.NONE); |
| item.setText(0, msg.data.time); |
| item.setText(1, new String(new char[] { msg.data.logLevel.getPriorityLetter() })); |
| item.setText(2, msg.data.pidString); |
| item.setText(3, msg.data.tag); |
| item.setText(4, msg.msg); |
| |
| // add the buffer index as data |
| item.setData(msg); |
| |
| if (msg.data.logLevel == LogLevel.INFO) { |
| item.setForeground(mColors.infoColor); |
| } else if (msg.data.logLevel == LogLevel.DEBUG) { |
| item.setForeground(mColors.debugColor); |
| } else if (msg.data.logLevel == LogLevel.ERROR) { |
| item.setForeground(mColors.errorColor); |
| } else if (msg.data.logLevel == LogLevel.WARN) { |
| item.setForeground(mColors.warningColor); |
| } else { |
| item.setForeground(mColors.verboseColor); |
| } |
| } |
| } |