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