Home | History | Annotate | Download | only in logcat
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ddmuilib.logcat;
     18 
     19 import com.android.ddmlib.IDevice;
     20 import com.android.ddmlib.Log;
     21 import com.android.ddmlib.MultiLineReceiver;
     22 import com.android.ddmlib.Log.LogLevel;
     23 import com.android.ddmuilib.DdmUiPreferences;
     24 import com.android.ddmuilib.IImageLoader;
     25 import com.android.ddmuilib.ITableFocusListener;
     26 import com.android.ddmuilib.SelectionDependentPanel;
     27 import com.android.ddmuilib.TableHelper;
     28 import com.android.ddmuilib.ITableFocusListener.IFocusedTableActivator;
     29 import com.android.ddmuilib.actions.ICommonAction;
     30 
     31 import org.eclipse.jface.preference.IPreferenceStore;
     32 import org.eclipse.swt.SWT;
     33 import org.eclipse.swt.SWTException;
     34 import org.eclipse.swt.dnd.Clipboard;
     35 import org.eclipse.swt.dnd.TextTransfer;
     36 import org.eclipse.swt.dnd.Transfer;
     37 import org.eclipse.swt.events.ControlEvent;
     38 import org.eclipse.swt.events.ControlListener;
     39 import org.eclipse.swt.events.FocusEvent;
     40 import org.eclipse.swt.events.FocusListener;
     41 import org.eclipse.swt.events.ModifyEvent;
     42 import org.eclipse.swt.events.ModifyListener;
     43 import org.eclipse.swt.events.SelectionAdapter;
     44 import org.eclipse.swt.events.SelectionEvent;
     45 import org.eclipse.swt.graphics.Font;
     46 import org.eclipse.swt.graphics.Rectangle;
     47 import org.eclipse.swt.layout.FillLayout;
     48 import org.eclipse.swt.layout.GridData;
     49 import org.eclipse.swt.layout.GridLayout;
     50 import org.eclipse.swt.widgets.Composite;
     51 import org.eclipse.swt.widgets.Control;
     52 import org.eclipse.swt.widgets.Display;
     53 import org.eclipse.swt.widgets.FileDialog;
     54 import org.eclipse.swt.widgets.Label;
     55 import org.eclipse.swt.widgets.TabFolder;
     56 import org.eclipse.swt.widgets.TabItem;
     57 import org.eclipse.swt.widgets.Table;
     58 import org.eclipse.swt.widgets.TableColumn;
     59 import org.eclipse.swt.widgets.TableItem;
     60 import org.eclipse.swt.widgets.Text;
     61 
     62 import java.io.FileWriter;
     63 import java.io.IOException;
     64 import java.util.ArrayList;
     65 import java.util.Arrays;
     66 import java.util.regex.Matcher;
     67 import java.util.regex.Pattern;
     68 
     69 public class LogPanel extends SelectionDependentPanel {
     70 
     71     private static final int STRING_BUFFER_LENGTH = 10000;
     72 
     73     /** no filtering. Only one tab with everything. */
     74     public static final int FILTER_NONE = 0;
     75     /** manual mode for filter. all filters are manually created. */
     76     public static final int FILTER_MANUAL = 1;
     77     /** automatic mode for filter (pid mode).
     78      * All filters are automatically created. */
     79     public static final int FILTER_AUTO_PID = 2;
     80     /** automatic mode for filter (tag mode).
     81      * All filters are automatically created. */
     82     public static final int FILTER_AUTO_TAG = 3;
     83     /** Manual filtering mode + new filter for debug app, if needed */
     84     public static final int FILTER_DEBUG = 4;
     85 
     86     public static final int COLUMN_MODE_MANUAL = 0;
     87     public static final int COLUMN_MODE_AUTO = 1;
     88 
     89     public static String PREFS_TIME;
     90     public static String PREFS_LEVEL;
     91     public static String PREFS_PID;
     92     public static String PREFS_TAG;
     93     public static String PREFS_MESSAGE;
     94 
     95     /**
     96      * This pattern is meant to parse the first line of a log message with the option
     97      * 'logcat -v long'. The first line represents the date, tag, severity, etc.. while the
     98      * following lines are the message (can be several line).<br>
     99      * This first line looks something like<br>
    100      * <code>"[ 00-00 00:00:00.000 &lt;pid&gt;:0x&lt;???&gt; &lt;severity&gt;/&lt;tag&gt;]"</code>
    101      * <br>
    102      * Note: severity is one of V, D, I, W, or EM<br>
    103      * Note: the fraction of second value can have any number of digit.
    104      * Note the tag should be trim as it may have spaces at the end.
    105      */
    106     private static Pattern sLogPattern = Pattern.compile(
    107             "^\\[\\s(\\d\\d-\\d\\d\\s\\d\\d:\\d\\d:\\d\\d\\.\\d+)" + //$NON-NLS-1$
    108             "\\s+(\\d*):(0x[0-9a-fA-F]+)\\s([VDIWE])/(.*)\\]$"); //$NON-NLS-1$
    109 
    110     /**
    111      * Interface for Storage Filter manager. Implementation of this interface
    112      * provide a custom way to archive an reload filters.
    113      */
    114     public interface ILogFilterStorageManager {
    115 
    116         public LogFilter[] getFilterFromStore();
    117 
    118         public void saveFilters(LogFilter[] filters);
    119 
    120         public boolean requiresDefaultFilter();
    121     }
    122 
    123     private Composite mParent;
    124     private IPreferenceStore mStore;
    125 
    126     /** top object in the view */
    127     private TabFolder mFolders;
    128 
    129     private LogColors mColors;
    130 
    131     private ILogFilterStorageManager mFilterStorage;
    132 
    133     private LogCatOuputReceiver mCurrentLogCat;
    134 
    135     /**
    136      * Circular buffer containing the logcat output. This is unfiltered.
    137      * The valid content goes from <code>mBufferStart</code> to
    138      * <code>mBufferEnd - 1</code>. Therefore its number of item is
    139      * <code>mBufferEnd - mBufferStart</code>.
    140      */
    141     private LogMessage[] mBuffer = new LogMessage[STRING_BUFFER_LENGTH];
    142 
    143     /** Represents the oldest message in the buffer */
    144     private int mBufferStart = -1;
    145 
    146     /**
    147      * Represents the next usable item in the buffer to receive new message.
    148      * This can be equal to mBufferStart, but when used mBufferStart will be
    149      * incremented as well.
    150      */
    151     private int mBufferEnd = -1;
    152 
    153     /** Filter list */
    154     private LogFilter[] mFilters;
    155 
    156     /** Default filter */
    157     private LogFilter mDefaultFilter;
    158 
    159     /** Current filter being displayed */
    160     private LogFilter mCurrentFilter;
    161 
    162     /** Filtering mode */
    163     private int mFilterMode = FILTER_NONE;
    164 
    165     /** Device currently running logcat */
    166     private IDevice mCurrentLoggedDevice = null;
    167 
    168     private ICommonAction mDeleteFilterAction;
    169     private ICommonAction mEditFilterAction;
    170 
    171     private ICommonAction[] mLogLevelActions;
    172 
    173     /** message data, separated from content for multi line messages */
    174     protected static class LogMessageInfo {
    175         public LogLevel logLevel;
    176         public int pid;
    177         public String pidString;
    178         public String tag;
    179         public String time;
    180     }
    181 
    182     /** pointer to the latest LogMessageInfo. this is used for multi line
    183      * log message, to reuse the info regarding level, pid, etc...
    184      */
    185     private LogMessageInfo mLastMessageInfo = null;
    186 
    187     private boolean mPendingAsyncRefresh = false;
    188 
    189     /** loader for the images. the implementation will varie between standalone
    190      * app and eclipse plugin app and eclipse plugin. */
    191     private IImageLoader mImageLoader;
    192 
    193     private String mDefaultLogSave;
    194 
    195     private int mColumnMode = COLUMN_MODE_MANUAL;
    196     private Font mDisplayFont;
    197 
    198     private ITableFocusListener mGlobalListener;
    199 
    200     /** message data, separated from content for multi line messages */
    201     protected static class LogMessage {
    202         public LogMessageInfo data;
    203         public String msg;
    204 
    205         @Override
    206         public String toString() {
    207             return data.time + ": " //$NON-NLS-1$
    208                 + data.logLevel + "/" //$NON-NLS-1$
    209                 + data.tag + "(" //$NON-NLS-1$
    210                 + data.pidString + "): " //$NON-NLS-1$
    211                 + msg;
    212         }
    213     }
    214 
    215     /**
    216      * objects able to receive the output of a remote shell command,
    217      * specifically a logcat command in this case
    218      */
    219     private final class LogCatOuputReceiver extends MultiLineReceiver {
    220 
    221         public boolean isCancelled = false;
    222 
    223         public LogCatOuputReceiver() {
    224             super();
    225 
    226             setTrimLine(false);
    227         }
    228 
    229         @Override
    230         public void processNewLines(String[] lines) {
    231             if (isCancelled == false) {
    232                 processLogLines(lines);
    233             }
    234         }
    235 
    236         public boolean isCancelled() {
    237             return isCancelled;
    238         }
    239     }
    240 
    241     /**
    242      * Parser class for the output of a "ps" shell command executed on a device.
    243      * This class looks for a specific pid to find the process name from it.
    244      * Once found, the name is used to update a filter and a tab object
    245      *
    246      */
    247     private class PsOutputReceiver extends MultiLineReceiver {
    248 
    249         private LogFilter mFilter;
    250 
    251         private TabItem mTabItem;
    252 
    253         private int mPid;
    254 
    255         /** set to true when we've found the pid we're looking for */
    256         private boolean mDone = false;
    257 
    258         PsOutputReceiver(int pid, LogFilter filter, TabItem tabItem) {
    259             mPid = pid;
    260             mFilter = filter;
    261             mTabItem = tabItem;
    262         }
    263 
    264         public boolean isCancelled() {
    265             return mDone;
    266         }
    267 
    268         @Override
    269         public void processNewLines(String[] lines) {
    270             for (String line : lines) {
    271                 if (line.startsWith("USER")) { //$NON-NLS-1$
    272                     continue;
    273                 }
    274                 // get the pid.
    275                 int index = line.indexOf(' ');
    276                 if (index == -1) {
    277                     continue;
    278                 }
    279                 // look for the next non blank char
    280                 index++;
    281                 while (line.charAt(index) == ' ') {
    282                     index++;
    283                 }
    284 
    285                 // this is the start of the pid.
    286                 // look for the end.
    287                 int index2 = line.indexOf(' ', index);
    288 
    289                 // get the line
    290                 String pidStr = line.substring(index, index2);
    291                 int pid = Integer.parseInt(pidStr);
    292                 if (pid != mPid) {
    293                     continue;
    294                 } else {
    295                     // get the process name
    296                     index = line.lastIndexOf(' ');
    297                     final String name = line.substring(index + 1);
    298 
    299                     mFilter.setName(name);
    300 
    301                     // update the tab
    302                     Display d = mFolders.getDisplay();
    303                     d.asyncExec(new Runnable() {
    304                        public void run() {
    305                            mTabItem.setText(name);
    306                        }
    307                     });
    308 
    309                     // we're done with this ps.
    310                     mDone = true;
    311                     return;
    312                 }
    313             }
    314         }
    315 
    316     }
    317 
    318 
    319     /**
    320      * Create the log view with some default parameters
    321      * @param imageLoader the image loader.
    322      * @param colors The display color object
    323      * @param filterStorage the storage for user defined filters.
    324      * @param mode The filtering mode
    325      */
    326     public LogPanel(IImageLoader imageLoader, LogColors colors,
    327             ILogFilterStorageManager filterStorage, int mode) {
    328         mImageLoader = imageLoader;
    329         mColors = colors;
    330         mFilterMode = mode;
    331         mFilterStorage = filterStorage;
    332         mStore = DdmUiPreferences.getStore();
    333     }
    334 
    335     public void setActions(ICommonAction deleteAction, ICommonAction editAction,
    336             ICommonAction[] logLevelActions) {
    337         mDeleteFilterAction = deleteAction;
    338         mEditFilterAction = editAction;
    339         mLogLevelActions = logLevelActions;
    340     }
    341 
    342     /**
    343      * Sets the column mode. Must be called before creatUI
    344      * @param mode the column mode. Valid values are COLUMN_MOD_MANUAL and
    345      *  COLUMN_MODE_AUTO
    346      */
    347     public void setColumnMode(int mode) {
    348         mColumnMode  = mode;
    349     }
    350 
    351     /**
    352      * Sets the display font.
    353      * @param font The display font.
    354      */
    355     public void setFont(Font font) {
    356         mDisplayFont = font;
    357 
    358         if (mFilters != null) {
    359             for (LogFilter f : mFilters) {
    360                 Table table = f.getTable();
    361                 if (table != null) {
    362                     table.setFont(font);
    363                 }
    364             }
    365         }
    366 
    367         if (mDefaultFilter != null) {
    368             Table table = mDefaultFilter.getTable();
    369             if (table != null) {
    370                 table.setFont(font);
    371             }
    372         }
    373     }
    374 
    375     /**
    376      * Sent when a new device is selected. The new device can be accessed
    377      * with {@link #getCurrentDevice()}.
    378      */
    379     @Override
    380     public void deviceSelected() {
    381         startLogCat(getCurrentDevice());
    382     }
    383 
    384     /**
    385      * Sent when a new client is selected. The new client can be accessed
    386      * with {@link #getCurrentClient()}.
    387      */
    388     @Override
    389     public void clientSelected() {
    390         // pass
    391     }
    392 
    393 
    394     /**
    395      * Creates a control capable of displaying some information.  This is
    396      * called once, when the application is initializing, from the UI thread.
    397      */
    398     @Override
    399     protected Control createControl(Composite parent) {
    400         mParent = parent;
    401 
    402         Composite top = new Composite(parent, SWT.NONE);
    403         top.setLayoutData(new GridData(GridData.FILL_BOTH));
    404         top.setLayout(new GridLayout(1, false));
    405 
    406         // create the tab folder
    407         mFolders = new TabFolder(top, SWT.NONE);
    408         mFolders.setLayoutData(new GridData(GridData.FILL_BOTH));
    409         mFolders.addSelectionListener(new SelectionAdapter() {
    410             @Override
    411             public void widgetSelected(SelectionEvent e) {
    412                 if (mCurrentFilter != null) {
    413                     mCurrentFilter.setSelectedState(false);
    414                 }
    415                 mCurrentFilter = getCurrentFilter();
    416                 mCurrentFilter.setSelectedState(true);
    417                 updateColumns(mCurrentFilter.getTable());
    418                 if (mCurrentFilter.getTempFilterStatus()) {
    419                     initFilter(mCurrentFilter);
    420                 }
    421                 selectionChanged(mCurrentFilter);
    422             }
    423         });
    424 
    425 
    426         Composite bottom = new Composite(top, SWT.NONE);
    427         bottom.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    428         bottom.setLayout(new GridLayout(3, false));
    429 
    430         Label label = new Label(bottom, SWT.NONE);
    431         label.setText("Filter:");
    432 
    433         final Text filterText = new Text(bottom, SWT.SINGLE | SWT.BORDER);
    434         filterText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    435         filterText.addModifyListener(new ModifyListener() {
    436             public void modifyText(ModifyEvent e) {
    437                 updateFilteringWith(filterText.getText());
    438             }
    439         });
    440 
    441         /*
    442         Button addFilterBtn = new Button(bottom, SWT.NONE);
    443         addFilterBtn.setImage(mImageLoader.loadImage("add.png", //$NON-NLS-1$
    444                 addFilterBtn.getDisplay()));
    445         */
    446 
    447         // get the filters
    448         createFilters();
    449 
    450         // for each filter, create a tab.
    451         int index = 0;
    452 
    453         if (mDefaultFilter != null) {
    454             createTab(mDefaultFilter, index++, false);
    455         }
    456 
    457         if (mFilters != null) {
    458             for (LogFilter f : mFilters) {
    459                 createTab(f, index++, false);
    460             }
    461         }
    462 
    463         return top;
    464     }
    465 
    466     @Override
    467     protected void postCreation() {
    468         // pass
    469     }
    470 
    471     /**
    472      * Sets the focus to the proper object.
    473      */
    474     @Override
    475     public void setFocus() {
    476         mFolders.setFocus();
    477     }
    478 
    479 
    480     /**
    481      * Starts a new logcat and set mCurrentLogCat as the current receiver.
    482      * @param device the device to connect logcat to.
    483      */
    484     public void startLogCat(final IDevice device) {
    485         if (device == mCurrentLoggedDevice) {
    486             return;
    487         }
    488 
    489         // if we have a logcat already running
    490         if (mCurrentLoggedDevice != null) {
    491             stopLogCat(false);
    492             mCurrentLoggedDevice = null;
    493         }
    494 
    495         resetUI(false);
    496 
    497         if (device != null) {
    498             // create a new output receiver
    499             mCurrentLogCat = new LogCatOuputReceiver();
    500 
    501             // start the logcat in a different thread
    502             new Thread("Logcat")  { //$NON-NLS-1$
    503                 @Override
    504                 public void run() {
    505 
    506                     while (device.isOnline() == false &&
    507                             mCurrentLogCat != null &&
    508                             mCurrentLogCat.isCancelled == false) {
    509                         try {
    510                             sleep(2000);
    511                         } catch (InterruptedException e) {
    512                             return;
    513                         }
    514                     }
    515 
    516                     if (mCurrentLogCat == null || mCurrentLogCat.isCancelled) {
    517                         // logcat was stopped/cancelled before the device became ready.
    518                         return;
    519                     }
    520 
    521                     try {
    522                         mCurrentLoggedDevice = device;
    523                         device.executeShellCommand("logcat -v long", mCurrentLogCat); //$NON-NLS-1$
    524                     } catch (Exception e) {
    525                         Log.e("Logcat", e);
    526                     } finally {
    527                         // at this point the command is terminated.
    528                         mCurrentLogCat = null;
    529                         mCurrentLoggedDevice = null;
    530                     }
    531                 }
    532             }.start();
    533         }
    534     }
    535 
    536     /** Stop the current logcat */
    537     public void stopLogCat(boolean inUiThread) {
    538         if (mCurrentLogCat != null) {
    539             mCurrentLogCat.isCancelled = true;
    540 
    541             // when the thread finishes, no one will reference that object
    542             // and it'll be destroyed
    543             mCurrentLogCat = null;
    544 
    545             // reset the content buffer
    546             for (int i = 0 ; i < STRING_BUFFER_LENGTH; i++) {
    547                 mBuffer[i] = null;
    548             }
    549 
    550             // because it's a circular buffer, it's hard to know if
    551             // the array is empty with both start/end at 0 or if it's full
    552             // with both start/end at 0 as well. So to mean empty, we use -1
    553             mBufferStart = -1;
    554             mBufferEnd = -1;
    555 
    556             resetFilters();
    557             resetUI(inUiThread);
    558         }
    559     }
    560 
    561     /**
    562      * Adds a new Filter. This methods displays the UI to create the filter
    563      * and set up its parameters.<br>
    564      * <b>MUST</b> be called from the ui thread.
    565      *
    566      */
    567     public void addFilter() {
    568         EditFilterDialog dlg = new EditFilterDialog(mImageLoader,
    569                 mFolders.getShell());
    570         if (dlg.open()) {
    571             synchronized (mBuffer) {
    572                 // get the new filter in the array
    573                 LogFilter filter = dlg.getFilter();
    574                 addFilterToArray(filter);
    575 
    576                 int index = mFilters.length - 1;
    577                 if (mDefaultFilter != null) {
    578                     index++;
    579                 }
    580 
    581                 if (false) {
    582 
    583                     for (LogFilter f : mFilters) {
    584                         if (f.uiReady()) {
    585                             f.dispose();
    586                         }
    587                     }
    588                     if (mDefaultFilter != null && mDefaultFilter.uiReady()) {
    589                         mDefaultFilter.dispose();
    590                     }
    591 
    592                     // for each filter, create a tab.
    593                     int i = 0;
    594                     if (mFilters != null) {
    595                         for (LogFilter f : mFilters) {
    596                             createTab(f, i++, true);
    597                         }
    598                     }
    599                     if (mDefaultFilter != null) {
    600                         createTab(mDefaultFilter, i++, true);
    601                     }
    602                 } else {
    603 
    604                     // create ui for the filter.
    605                     createTab(filter, index, true);
    606 
    607                     // reset the default as it shouldn't contain the content of
    608                     // this new filter.
    609                     if (mDefaultFilter != null) {
    610                         initDefaultFilter();
    611                     }
    612                 }
    613 
    614                 // select the new filter
    615                 if (mCurrentFilter != null) {
    616                     mCurrentFilter.setSelectedState(false);
    617                 }
    618                 mFolders.setSelection(index);
    619                 filter.setSelectedState(true);
    620                 mCurrentFilter = filter;
    621 
    622                 selectionChanged(filter);
    623 
    624                 // finally we update the filtering mode if needed
    625                 if (mFilterMode == FILTER_NONE) {
    626                     mFilterMode = FILTER_MANUAL;
    627                 }
    628 
    629                 mFilterStorage.saveFilters(mFilters);
    630 
    631             }
    632         }
    633     }
    634 
    635     /**
    636      * Edits the current filter. The method displays the UI to edit the filter.
    637      */
    638     public void editFilter() {
    639         if (mCurrentFilter != null && mCurrentFilter != mDefaultFilter) {
    640             EditFilterDialog dlg = new EditFilterDialog(mImageLoader,
    641                     mFolders.getShell(),
    642                     mCurrentFilter);
    643             if (dlg.open()) {
    644                 synchronized (mBuffer) {
    645                     // at this point the filter has been updated.
    646                     // so we update its content
    647                     initFilter(mCurrentFilter);
    648 
    649                     // and the content of the "other" filter as well.
    650                     if (mDefaultFilter != null) {
    651                         initDefaultFilter();
    652                     }
    653 
    654                     mFilterStorage.saveFilters(mFilters);
    655                 }
    656             }
    657         }
    658     }
    659 
    660     /**
    661      * Deletes the current filter.
    662      */
    663     public void deleteFilter() {
    664         synchronized (mBuffer) {
    665             if (mCurrentFilter != null && mCurrentFilter != mDefaultFilter) {
    666                 // remove the filter from the list
    667                 removeFilterFromArray(mCurrentFilter);
    668                 mCurrentFilter.dispose();
    669 
    670                 // select the new filter
    671                 mFolders.setSelection(0);
    672                 if (mFilters.length > 0) {
    673                     mCurrentFilter = mFilters[0];
    674                 } else {
    675                     mCurrentFilter = mDefaultFilter;
    676                 }
    677 
    678                 selectionChanged(mCurrentFilter);
    679 
    680                 // update the content of the "other" filter to include what was filtered out
    681                 // by the deleted filter.
    682                 if (mDefaultFilter != null) {
    683                     initDefaultFilter();
    684                 }
    685 
    686                 mFilterStorage.saveFilters(mFilters);
    687             }
    688         }
    689     }
    690 
    691     /**
    692      * saves the current selection in a text file.
    693      * @return false if the saving failed.
    694      */
    695     public boolean save() {
    696         synchronized (mBuffer) {
    697             FileDialog dlg = new FileDialog(mParent.getShell(), SWT.SAVE);
    698             String fileName;
    699 
    700             dlg.setText("Save log...");
    701             dlg.setFileName("log.txt");
    702             String defaultPath = mDefaultLogSave;
    703             if (defaultPath == null) {
    704                 defaultPath = System.getProperty("user.home"); //$NON-NLS-1$
    705             }
    706             dlg.setFilterPath(defaultPath);
    707             dlg.setFilterNames(new String[] {
    708                 "Text Files (*.txt)"
    709             });
    710             dlg.setFilterExtensions(new String[] {
    711                 "*.txt"
    712             });
    713 
    714             fileName = dlg.open();
    715             if (fileName != null) {
    716                 mDefaultLogSave = dlg.getFilterPath();
    717 
    718                 // get the current table and its selection
    719                 Table currentTable = mCurrentFilter.getTable();
    720 
    721                 int[] selection = currentTable.getSelectionIndices();
    722 
    723                 // we need to sort the items to be sure.
    724                 Arrays.sort(selection);
    725 
    726                 // loop on the selection and output the file.
    727                 try {
    728                     FileWriter writer = new FileWriter(fileName);
    729 
    730                     for (int i : selection) {
    731                         TableItem item = currentTable.getItem(i);
    732                         LogMessage msg = (LogMessage)item.getData();
    733                         String line = msg.toString();
    734                         writer.write(line);
    735                         writer.write('\n');
    736                     }
    737                     writer.flush();
    738 
    739                 } catch (IOException e) {
    740                     return false;
    741                 }
    742             }
    743         }
    744 
    745         return true;
    746     }
    747 
    748     /**
    749      * Empty the current circular buffer.
    750      */
    751     public void clear() {
    752         synchronized (mBuffer) {
    753             for (int i = 0 ; i < STRING_BUFFER_LENGTH; i++) {
    754                 mBuffer[i] = null;
    755             }
    756 
    757             mBufferStart = -1;
    758             mBufferEnd = -1;
    759 
    760             // now we clear the existing filters
    761             for (LogFilter filter : mFilters) {
    762                 filter.clear();
    763             }
    764 
    765             // and the default one
    766             if (mDefaultFilter != null) {
    767                 mDefaultFilter.clear();
    768             }
    769         }
    770     }
    771 
    772     /**
    773      * Copies the current selection of the current filter as multiline text.
    774      *
    775      * @param clipboard The clipboard to place the copied content.
    776      */
    777     public void copy(Clipboard clipboard) {
    778         // get the current table and its selection
    779         Table currentTable = mCurrentFilter.getTable();
    780 
    781         copyTable(clipboard, currentTable);
    782     }
    783 
    784     /**
    785      * Selects all lines.
    786      */
    787     public void selectAll() {
    788         Table currentTable = mCurrentFilter.getTable();
    789         currentTable.selectAll();
    790     }
    791 
    792     /**
    793      * Sets a TableFocusListener which will be notified when one of the tables
    794      * gets or loses focus.
    795      *
    796      * @param listener
    797      */
    798     public void setTableFocusListener(ITableFocusListener listener) {
    799         // record the global listener, to make sure table created after
    800         // this call will still be setup.
    801         mGlobalListener = listener;
    802 
    803         // now we setup the existing filters
    804         for (LogFilter filter : mFilters) {
    805             Table table = filter.getTable();
    806 
    807             addTableToFocusListener(table);
    808         }
    809 
    810         // and the default one
    811         if (mDefaultFilter != null) {
    812             addTableToFocusListener(mDefaultFilter.getTable());
    813         }
    814     }
    815 
    816     /**
    817      * Sets up a Table object to notify the global Table Focus listener when it
    818      * gets or loses the focus.
    819      *
    820      * @param table the Table object.
    821      */
    822     private void addTableToFocusListener(final Table table) {
    823         // create the activator for this table
    824         final IFocusedTableActivator activator = new IFocusedTableActivator() {
    825             public void copy(Clipboard clipboard) {
    826                 copyTable(clipboard, table);
    827             }
    828 
    829             public void selectAll() {
    830                 table.selectAll();
    831             }
    832         };
    833 
    834         // add the focus listener on the table to notify the global listener
    835         table.addFocusListener(new FocusListener() {
    836             public void focusGained(FocusEvent e) {
    837                 mGlobalListener.focusGained(activator);
    838             }
    839 
    840             public void focusLost(FocusEvent e) {
    841                 mGlobalListener.focusLost(activator);
    842             }
    843         });
    844     }
    845 
    846     /**
    847      * Copies the current selection of a Table into the provided Clipboard, as
    848      * multi-line text.
    849      *
    850      * @param clipboard The clipboard to place the copied content.
    851      * @param table The table to copy from.
    852      */
    853     private static void copyTable(Clipboard clipboard, Table table) {
    854         int[] selection = table.getSelectionIndices();
    855 
    856         // we need to sort the items to be sure.
    857         Arrays.sort(selection);
    858 
    859         // all lines must be concatenated.
    860         StringBuilder sb = new StringBuilder();
    861 
    862         // loop on the selection and output the file.
    863         for (int i : selection) {
    864             TableItem item = table.getItem(i);
    865             LogMessage msg = (LogMessage)item.getData();
    866             String line = msg.toString();
    867             sb.append(line);
    868             sb.append('\n');
    869         }
    870 
    871         // now add that to the clipboard
    872         clipboard.setContents(new Object[] {
    873             sb.toString()
    874         }, new Transfer[] {
    875             TextTransfer.getInstance()
    876         });
    877     }
    878 
    879     /**
    880      * Sets the log level for the current filter, but does not save it.
    881      * @param i
    882      */
    883     public void setCurrentFilterLogLevel(int i) {
    884         LogFilter filter = getCurrentFilter();
    885 
    886         filter.setLogLevel(i);
    887 
    888         initFilter(filter);
    889     }
    890 
    891     /**
    892      * Creates a new tab in the folderTab item. Must be called from the ui
    893      *      thread.
    894      * @param filter The filter associated with the tab.
    895      * @param index the index of the tab. if -1, the tab will be added at the
    896      *          end.
    897      * @param fillTable If true the table is filled with the current content of
    898      *          the buffer.
    899      * @return The TabItem object that was created.
    900      */
    901     private TabItem createTab(LogFilter filter, int index, boolean fillTable) {
    902         synchronized (mBuffer) {
    903             TabItem item = null;
    904             if (index != -1) {
    905                 item = new TabItem(mFolders, SWT.NONE, index);
    906             } else {
    907                 item = new TabItem(mFolders, SWT.NONE);
    908             }
    909             item.setText(filter.getName());
    910 
    911             // set the control (the parent is the TabFolder item, always)
    912             Composite top = new Composite(mFolders, SWT.NONE);
    913             item.setControl(top);
    914 
    915             top.setLayout(new FillLayout());
    916 
    917             // create the ui, first the table
    918             final Table t = new Table(top, SWT.MULTI | SWT.FULL_SELECTION);
    919 
    920             if (mDisplayFont != null) {
    921                 t.setFont(mDisplayFont);
    922             }
    923 
    924             // give the ui objects to the filters.
    925             filter.setWidgets(item, t);
    926 
    927             t.setHeaderVisible(true);
    928             t.setLinesVisible(false);
    929 
    930             if (mGlobalListener != null) {
    931                 addTableToFocusListener(t);
    932             }
    933 
    934             // create a controllistener that will handle the resizing of all the
    935             // columns (except the last) and of the table itself.
    936             ControlListener listener = null;
    937             if (mColumnMode == COLUMN_MODE_AUTO) {
    938                 listener = new ControlListener() {
    939                     public void controlMoved(ControlEvent e) {
    940                     }
    941 
    942                     public void controlResized(ControlEvent e) {
    943                         Rectangle r = t.getClientArea();
    944 
    945                         // get the size of all but the last column
    946                         int total = t.getColumn(0).getWidth();
    947                         total += t.getColumn(1).getWidth();
    948                         total += t.getColumn(2).getWidth();
    949                         total += t.getColumn(3).getWidth();
    950 
    951                         if (r.width > total) {
    952                             t.getColumn(4).setWidth(r.width-total);
    953                         }
    954                     }
    955                 };
    956 
    957                 t.addControlListener(listener);
    958             }
    959 
    960             // then its column
    961             TableColumn col = TableHelper.createTableColumn(t, "Time", SWT.LEFT,
    962                     "00-00 00:00:00", //$NON-NLS-1$
    963                     PREFS_TIME, mStore);
    964             if (mColumnMode == COLUMN_MODE_AUTO) {
    965                 col.addControlListener(listener);
    966             }
    967 
    968             col = TableHelper.createTableColumn(t, "", SWT.CENTER,
    969                     "D", //$NON-NLS-1$
    970                     PREFS_LEVEL, mStore);
    971             if (mColumnMode == COLUMN_MODE_AUTO) {
    972                 col.addControlListener(listener);
    973             }
    974 
    975             col = TableHelper.createTableColumn(t, "pid", SWT.LEFT,
    976                     "9999", //$NON-NLS-1$
    977                     PREFS_PID, mStore);
    978             if (mColumnMode == COLUMN_MODE_AUTO) {
    979                 col.addControlListener(listener);
    980             }
    981 
    982             col = TableHelper.createTableColumn(t, "tag", SWT.LEFT,
    983                     "abcdefgh",  //$NON-NLS-1$
    984                     PREFS_TAG, mStore);
    985             if (mColumnMode == COLUMN_MODE_AUTO) {
    986                 col.addControlListener(listener);
    987             }
    988 
    989             col = TableHelper.createTableColumn(t, "Message", SWT.LEFT,
    990                     "abcdefghijklmnopqrstuvwxyz0123456789",  //$NON-NLS-1$
    991                     PREFS_MESSAGE, mStore);
    992             if (mColumnMode == COLUMN_MODE_AUTO) {
    993                 // instead of listening on resize for the last column, we make
    994                 // it non resizable.
    995                 col.setResizable(false);
    996             }
    997 
    998             if (fillTable) {
    999                 initFilter(filter);
   1000             }
   1001             return item;
   1002         }
   1003     }
   1004 
   1005     protected void updateColumns(Table table) {
   1006         if (table != null) {
   1007             int index = 0;
   1008             TableColumn col;
   1009 
   1010             col = table.getColumn(index++);
   1011             col.setWidth(mStore.getInt(PREFS_TIME));
   1012 
   1013             col = table.getColumn(index++);
   1014             col.setWidth(mStore.getInt(PREFS_LEVEL));
   1015 
   1016             col = table.getColumn(index++);
   1017             col.setWidth(mStore.getInt(PREFS_PID));
   1018 
   1019             col = table.getColumn(index++);
   1020             col.setWidth(mStore.getInt(PREFS_TAG));
   1021 
   1022             col = table.getColumn(index++);
   1023             col.setWidth(mStore.getInt(PREFS_MESSAGE));
   1024         }
   1025     }
   1026 
   1027     public void resetUI(boolean inUiThread) {
   1028         if (mFilterMode == FILTER_AUTO_PID || mFilterMode == FILTER_AUTO_TAG) {
   1029             if (inUiThread) {
   1030                 mFolders.dispose();
   1031                 mParent.pack(true);
   1032                 createControl(mParent);
   1033             } else {
   1034                 Display d = mFolders.getDisplay();
   1035 
   1036                 // run sync as we need to update right now.
   1037                 d.syncExec(new Runnable() {
   1038                     public void run() {
   1039                         mFolders.dispose();
   1040                         mParent.pack(true);
   1041                         createControl(mParent);
   1042                     }
   1043                 });
   1044             }
   1045         } else  {
   1046             // the ui is static we just empty it.
   1047             if (mFolders.isDisposed() == false) {
   1048                 if (inUiThread) {
   1049                     emptyTables();
   1050                 } else {
   1051                     Display d = mFolders.getDisplay();
   1052 
   1053                     // run sync as we need to update right now.
   1054                     d.syncExec(new Runnable() {
   1055                         public void run() {
   1056                             if (mFolders.isDisposed() == false) {
   1057                                 emptyTables();
   1058                             }
   1059                         }
   1060                     });
   1061                 }
   1062             }
   1063         }
   1064     }
   1065 
   1066     /**
   1067      * Process new Log lines coming from {@link LogCatOuputReceiver}.
   1068      * @param lines the new lines
   1069      */
   1070     protected void processLogLines(String[] lines) {
   1071         // WARNING: this will not work if the string contains more line than
   1072         // the buffer holds.
   1073 
   1074         if (lines.length > STRING_BUFFER_LENGTH) {
   1075             Log.e("LogCat", "Receiving more lines than STRING_BUFFER_LENGTH");
   1076         }
   1077 
   1078         // parse the lines and create LogMessage that are stored in a temporary list
   1079         final ArrayList<LogMessage> newMessages = new ArrayList<LogMessage>();
   1080 
   1081         synchronized (mBuffer) {
   1082             for (String line : lines) {
   1083                 // ignore empty lines.
   1084                 if (line.length() > 0) {
   1085                     // check for header lines.
   1086                     Matcher matcher = sLogPattern.matcher(line);
   1087                     if (matcher.matches()) {
   1088                         // this is a header line, parse the header and keep it around.
   1089                         mLastMessageInfo = new LogMessageInfo();
   1090 
   1091                         mLastMessageInfo.time = matcher.group(1);
   1092                         mLastMessageInfo.pidString = matcher.group(2);
   1093                         mLastMessageInfo.pid = Integer.valueOf(mLastMessageInfo.pidString);
   1094                         mLastMessageInfo.logLevel = LogLevel.getByLetterString(matcher.group(4));
   1095                         mLastMessageInfo.tag = matcher.group(5).trim();
   1096                     } else {
   1097                         // This is not a header line.
   1098                         // Create a new LogMessage and process it.
   1099                         LogMessage mc = new LogMessage();
   1100 
   1101                         if (mLastMessageInfo == null) {
   1102                             // The first line of output wasn't preceded
   1103                             // by a header line; make something up so
   1104                             // that users of mc.data don't NPE.
   1105                             mLastMessageInfo = new LogMessageInfo();
   1106                             mLastMessageInfo.time = "??-?? ??:??:??.???"; //$NON-NLS1$
   1107                             mLastMessageInfo.pidString = "<unknown>"; //$NON-NLS1$
   1108                             mLastMessageInfo.pid = 0;
   1109                             mLastMessageInfo.logLevel = LogLevel.INFO;
   1110                             mLastMessageInfo.tag = "<unknown>"; //$NON-NLS1$
   1111                         }
   1112 
   1113                         // If someone printed a log message with
   1114                         // embedded '\n' characters, there will
   1115                         // one header line followed by multiple text lines.
   1116                         // Use the last header that we saw.
   1117                         mc.data = mLastMessageInfo;
   1118 
   1119                         // tabs seem to display as only 1 tab so we replace the leading tabs
   1120                         // by 4 spaces.
   1121                         mc.msg = line.replaceAll("\t", "    "); //$NON-NLS-1$ //$NON-NLS-2$
   1122 
   1123                         // process the new LogMessage.
   1124                         processNewMessage(mc);
   1125 
   1126                         // store the new LogMessage
   1127                         newMessages.add(mc);
   1128                     }
   1129                 }
   1130             }
   1131 
   1132             // if we don't have a pending Runnable that will do the refresh, we ask the Display
   1133             // to run one in the UI thread.
   1134             if (mPendingAsyncRefresh == false) {
   1135                 mPendingAsyncRefresh = true;
   1136 
   1137                 try {
   1138                     Display display = mFolders.getDisplay();
   1139 
   1140                     // run in sync because this will update the buffer start/end indices
   1141                     display.asyncExec(new Runnable() {
   1142                         public void run() {
   1143                             asyncRefresh();
   1144                         }
   1145                     });
   1146                 } catch (SWTException e) {
   1147                     // display is disposed, we're probably quitting. Let's stop.
   1148                     stopLogCat(false);
   1149                 }
   1150             }
   1151         }
   1152     }
   1153 
   1154     /**
   1155      * Refreshes the UI with new messages.
   1156      */
   1157     private void asyncRefresh() {
   1158         if (mFolders.isDisposed() == false) {
   1159             synchronized (mBuffer) {
   1160                 try {
   1161                     // the circular buffer has been updated, let have the filter flush their
   1162                     // display with the new messages.
   1163                     if (mFilters != null) {
   1164                         for (LogFilter f : mFilters) {
   1165                             f.flush();
   1166                         }
   1167                     }
   1168 
   1169                     if (mDefaultFilter != null) {
   1170                         mDefaultFilter.flush();
   1171                     }
   1172                 } finally {
   1173                     // the pending refresh is done.
   1174                     mPendingAsyncRefresh = false;
   1175                 }
   1176             }
   1177         } else {
   1178             stopLogCat(true);
   1179         }
   1180     }
   1181 
   1182     /**
   1183      * Processes a new Message.
   1184      * <p/>This adds the new message to the buffer, and gives it to the existing filters.
   1185      * @param newMessage
   1186      */
   1187     private void processNewMessage(LogMessage newMessage) {
   1188         // if we are in auto filtering mode, make sure we have
   1189         // a filter for this
   1190         if (mFilterMode == FILTER_AUTO_PID ||
   1191                 mFilterMode == FILTER_AUTO_TAG) {
   1192            checkFilter(newMessage.data);
   1193         }
   1194 
   1195         // compute the index where the message goes.
   1196         // was the buffer empty?
   1197         int messageIndex = -1;
   1198         if (mBufferStart == -1) {
   1199             messageIndex = mBufferStart = 0;
   1200             mBufferEnd = 1;
   1201         } else {
   1202             messageIndex = mBufferEnd;
   1203 
   1204             // check we aren't overwriting start
   1205             if (mBufferEnd == mBufferStart) {
   1206                 mBufferStart = (mBufferStart + 1) % STRING_BUFFER_LENGTH;
   1207             }
   1208 
   1209             // increment the next usable slot index
   1210             mBufferEnd = (mBufferEnd + 1) % STRING_BUFFER_LENGTH;
   1211         }
   1212 
   1213         LogMessage oldMessage = null;
   1214 
   1215         // record the message that was there before
   1216         if (mBuffer[messageIndex] != null) {
   1217             oldMessage = mBuffer[messageIndex];
   1218         }
   1219 
   1220         // then add the new one
   1221         mBuffer[messageIndex] = newMessage;
   1222 
   1223         // give the new message to every filters.
   1224         boolean filtered = false;
   1225         if (mFilters != null) {
   1226             for (LogFilter f : mFilters) {
   1227                 filtered |= f.addMessage(newMessage, oldMessage);
   1228             }
   1229         }
   1230         if (filtered == false && mDefaultFilter != null) {
   1231             mDefaultFilter.addMessage(newMessage, oldMessage);
   1232         }
   1233     }
   1234 
   1235     private void createFilters() {
   1236         if (mFilterMode == FILTER_DEBUG || mFilterMode == FILTER_MANUAL) {
   1237             // unarchive the filters.
   1238             mFilters = mFilterStorage.getFilterFromStore();
   1239 
   1240             // set the colors
   1241             if (mFilters != null) {
   1242                 for (LogFilter f : mFilters) {
   1243                     f.setColors(mColors);
   1244                 }
   1245             }
   1246 
   1247             if (mFilterStorage.requiresDefaultFilter()) {
   1248                 mDefaultFilter = new LogFilter("Log");
   1249                 mDefaultFilter.setColors(mColors);
   1250                 mDefaultFilter.setSupportsDelete(false);
   1251                 mDefaultFilter.setSupportsEdit(false);
   1252             }
   1253         } else if (mFilterMode == FILTER_NONE) {
   1254             // if the filtering mode is "none", we create a single filter that
   1255             // will receive all
   1256             mDefaultFilter = new LogFilter("Log");
   1257             mDefaultFilter.setColors(mColors);
   1258             mDefaultFilter.setSupportsDelete(false);
   1259             mDefaultFilter.setSupportsEdit(false);
   1260         }
   1261     }
   1262 
   1263     /** Checks if there's an automatic filter for this md and if not
   1264      * adds the filter and the ui.
   1265      * This must be called from the UI!
   1266      * @param md
   1267      * @return true if the filter existed already
   1268      */
   1269     private boolean checkFilter(final LogMessageInfo md) {
   1270         if (true)
   1271             return true;
   1272         // look for a filter that matches the pid
   1273         if (mFilterMode == FILTER_AUTO_PID) {
   1274             for (LogFilter f : mFilters) {
   1275                 if (f.getPidFilter() == md.pid) {
   1276                     return true;
   1277                 }
   1278             }
   1279         } else if (mFilterMode == FILTER_AUTO_TAG) {
   1280             for (LogFilter f : mFilters) {
   1281                 if (f.getTagFilter().equals(md.tag)) {
   1282                     return true;
   1283                 }
   1284             }
   1285         }
   1286 
   1287         // if we reach this point, no filter was found.
   1288         // create a filter with a temporary name of the pid
   1289         final LogFilter newFilter = new LogFilter(md.pidString);
   1290         String name = null;
   1291         if (mFilterMode == FILTER_AUTO_PID) {
   1292             newFilter.setPidMode(md.pid);
   1293 
   1294             // ask the monitor thread if it knows the pid.
   1295             name = mCurrentLoggedDevice.getClientName(md.pid);
   1296         } else {
   1297             newFilter.setTagMode(md.tag);
   1298             name = md.tag;
   1299         }
   1300         addFilterToArray(newFilter);
   1301 
   1302         final String fname = name;
   1303 
   1304         // create the tabitem
   1305         final TabItem newTabItem = createTab(newFilter, -1, true);
   1306 
   1307         // if the name is unknown
   1308         if (fname == null) {
   1309             // we need to find the process running under that pid.
   1310             // launch a thread do a ps on the device
   1311             new Thread("remote PS") { //$NON-NLS-1$
   1312                 @Override
   1313                 public void run() {
   1314                     // create the receiver
   1315                     PsOutputReceiver psor = new PsOutputReceiver(md.pid,
   1316                             newFilter, newTabItem);
   1317 
   1318                     // execute ps
   1319                     try {
   1320                         mCurrentLoggedDevice.executeShellCommand("ps", psor); //$NON-NLS-1$
   1321                     } catch (IOException e) {
   1322                         // hmm...
   1323                     }
   1324                 }
   1325             }.start();
   1326         }
   1327 
   1328         return false;
   1329     }
   1330 
   1331     /**
   1332      * Adds a new filter to the current filter array, and set its colors
   1333      * @param newFilter The filter to add
   1334      */
   1335     private void addFilterToArray(LogFilter newFilter) {
   1336         // set the colors
   1337         newFilter.setColors(mColors);
   1338 
   1339         // add it to the array.
   1340         if (mFilters != null && mFilters.length > 0) {
   1341             LogFilter[] newFilters = new LogFilter[mFilters.length+1];
   1342             System.arraycopy(mFilters, 0, newFilters, 0, mFilters.length);
   1343             newFilters[mFilters.length] = newFilter;
   1344             mFilters = newFilters;
   1345         } else {
   1346             mFilters = new LogFilter[1];
   1347             mFilters[0] = newFilter;
   1348         }
   1349     }
   1350 
   1351     private void removeFilterFromArray(LogFilter oldFilter) {
   1352         // look for the index
   1353         int index = -1;
   1354         for (int i = 0 ; i < mFilters.length ; i++) {
   1355             if (mFilters[i] == oldFilter) {
   1356                 index = i;
   1357                 break;
   1358             }
   1359         }
   1360 
   1361         if (index != -1) {
   1362             LogFilter[] newFilters = new LogFilter[mFilters.length-1];
   1363             System.arraycopy(mFilters, 0, newFilters, 0, index);
   1364             System.arraycopy(mFilters, index + 1, newFilters, index,
   1365                     newFilters.length-index);
   1366             mFilters = newFilters;
   1367         }
   1368     }
   1369 
   1370     /**
   1371      * Initialize the filter with already existing buffer.
   1372      * @param filter
   1373      */
   1374     private void initFilter(LogFilter filter) {
   1375         // is it empty
   1376         if (filter.uiReady() == false) {
   1377             return;
   1378         }
   1379 
   1380         if (filter == mDefaultFilter) {
   1381             initDefaultFilter();
   1382             return;
   1383         }
   1384 
   1385         filter.clear();
   1386 
   1387         if (mBufferStart != -1) {
   1388             int max = mBufferEnd;
   1389             if (mBufferEnd < mBufferStart) {
   1390                 max += STRING_BUFFER_LENGTH;
   1391             }
   1392 
   1393             for (int i = mBufferStart; i < max; i++) {
   1394                 int realItemIndex = i % STRING_BUFFER_LENGTH;
   1395 
   1396                 filter.addMessage(mBuffer[realItemIndex], null /* old message */);
   1397             }
   1398         }
   1399 
   1400         filter.flush();
   1401         filter.resetTempFilteringStatus();
   1402     }
   1403 
   1404     /**
   1405      * Refill the default filter. Not to be called directly.
   1406      * @see initFilter()
   1407      */
   1408     private void initDefaultFilter() {
   1409         mDefaultFilter.clear();
   1410 
   1411         if (mBufferStart != -1) {
   1412             int max = mBufferEnd;
   1413             if (mBufferEnd < mBufferStart) {
   1414                 max += STRING_BUFFER_LENGTH;
   1415             }
   1416 
   1417             for (int i = mBufferStart; i < max; i++) {
   1418                 int realItemIndex = i % STRING_BUFFER_LENGTH;
   1419                 LogMessage msg = mBuffer[realItemIndex];
   1420 
   1421                 // first we check that the other filters don't take this message
   1422                 boolean filtered = false;
   1423                 for (LogFilter f : mFilters) {
   1424                     filtered |= f.accept(msg);
   1425                 }
   1426 
   1427                 if (filtered == false) {
   1428                     mDefaultFilter.addMessage(msg, null /* old message */);
   1429                 }
   1430             }
   1431         }
   1432 
   1433         mDefaultFilter.flush();
   1434         mDefaultFilter.resetTempFilteringStatus();
   1435     }
   1436 
   1437     /**
   1438      * Reset the filters, to handle change in device in automatic filter mode
   1439      */
   1440     private void resetFilters() {
   1441         // if we are in automatic mode, then we need to rmove the current
   1442         // filter.
   1443         if (mFilterMode == FILTER_AUTO_PID || mFilterMode == FILTER_AUTO_TAG) {
   1444             mFilters = null;
   1445 
   1446             // recreate the filters.
   1447             createFilters();
   1448         }
   1449     }
   1450 
   1451 
   1452     private LogFilter getCurrentFilter() {
   1453         int index = mFolders.getSelectionIndex();
   1454 
   1455         // if mFilters is null or index is invalid, we return the default
   1456         // filter. It doesn't matter if that one is null as well, since we
   1457         // would return null anyway.
   1458         if (index == 0 || mFilters == null) {
   1459             return mDefaultFilter;
   1460         }
   1461 
   1462         return mFilters[index-1];
   1463     }
   1464 
   1465 
   1466     private void emptyTables() {
   1467         for (LogFilter f : mFilters) {
   1468             f.getTable().removeAll();
   1469         }
   1470 
   1471         if (mDefaultFilter != null) {
   1472             mDefaultFilter.getTable().removeAll();
   1473         }
   1474     }
   1475 
   1476     protected void updateFilteringWith(String text) {
   1477         synchronized (mBuffer) {
   1478             // reset the temp filtering for all the filters
   1479             for (LogFilter f : mFilters) {
   1480                 f.resetTempFiltering();
   1481             }
   1482             if (mDefaultFilter != null) {
   1483                 mDefaultFilter.resetTempFiltering();
   1484             }
   1485 
   1486             // now we need to figure out the new temp filtering
   1487             // split each word
   1488             String[] segments = text.split(" "); //$NON-NLS-1$
   1489 
   1490             ArrayList<String> keywords = new ArrayList<String>(segments.length);
   1491 
   1492             // loop and look for temp id/tag
   1493             int tempPid = -1;
   1494             String tempTag = null;
   1495             for (int i = 0 ; i < segments.length; i++) {
   1496                 String s = segments[i];
   1497                 if (tempPid == -1 && s.startsWith("pid:")) { //$NON-NLS-1$
   1498                     // get the pid
   1499                     String[] seg = s.split(":"); //$NON-NLS-1$
   1500                     if (seg.length == 2) {
   1501                         if (seg[1].matches("^[0-9]*$")) { //$NON-NLS-1$
   1502                             tempPid = Integer.valueOf(seg[1]);
   1503                         }
   1504                     }
   1505                 } else if (tempTag == null && s.startsWith("tag:")) { //$NON-NLS-1$
   1506                     String seg[] = segments[i].split(":"); //$NON-NLS-1$
   1507                     if (seg.length == 2) {
   1508                         tempTag = seg[1];
   1509                     }
   1510                 } else {
   1511                     keywords.add(s);
   1512                 }
   1513             }
   1514 
   1515             // set the temp filtering in the filters
   1516             if (tempPid != -1 || tempTag != null || keywords.size() > 0) {
   1517                 String[] keywordsArray = keywords.toArray(
   1518                         new String[keywords.size()]);
   1519 
   1520                 for (LogFilter f : mFilters) {
   1521                     if (tempPid != -1) {
   1522                         f.setTempPidFiltering(tempPid);
   1523                     }
   1524                     if (tempTag != null) {
   1525                         f.setTempTagFiltering(tempTag);
   1526                     }
   1527                     f.setTempKeywordFiltering(keywordsArray);
   1528                 }
   1529 
   1530                 if (mDefaultFilter != null) {
   1531                     if (tempPid != -1) {
   1532                         mDefaultFilter.setTempPidFiltering(tempPid);
   1533                     }
   1534                     if (tempTag != null) {
   1535                         mDefaultFilter.setTempTagFiltering(tempTag);
   1536                     }
   1537                     mDefaultFilter.setTempKeywordFiltering(keywordsArray);
   1538 
   1539                 }
   1540             }
   1541 
   1542             initFilter(mCurrentFilter);
   1543         }
   1544     }
   1545 
   1546     /**
   1547      * Called when the current filter selection changes.
   1548      * @param selectedFilter
   1549      */
   1550     private void selectionChanged(LogFilter selectedFilter) {
   1551         if (mLogLevelActions != null) {
   1552             // get the log level
   1553             int level = selectedFilter.getLogLevel();
   1554             for (int i = 0 ; i < mLogLevelActions.length; i++) {
   1555                 ICommonAction a = mLogLevelActions[i];
   1556                 if (i == level - 2) {
   1557                     a.setChecked(true);
   1558                 } else {
   1559                     a.setChecked(false);
   1560                 }
   1561             }
   1562         }
   1563 
   1564         if (mDeleteFilterAction != null) {
   1565             mDeleteFilterAction.setEnabled(selectedFilter.supportsDelete());
   1566         }
   1567         if (mEditFilterAction != null) {
   1568             mEditFilterAction.setEnabled(selectedFilter.supportsEdit());
   1569         }
   1570     }
   1571 
   1572     public String getSelectedErrorLineMessage() {
   1573         Table table = mCurrentFilter.getTable();
   1574         int[] selection = table.getSelectionIndices();
   1575 
   1576         if (selection.length == 1) {
   1577             TableItem item = table.getItem(selection[0]);
   1578             LogMessage msg = (LogMessage)item.getData();
   1579             if (msg.data.logLevel == LogLevel.ERROR || msg.data.logLevel == LogLevel.WARN)
   1580                 return msg.msg;
   1581         }
   1582         return null;
   1583     }
   1584 }
   1585