1 /* 2 * Copyright (C) 2008 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.log.event; 18 19 import com.android.ddmlib.Client; 20 import com.android.ddmlib.IDevice; 21 import com.android.ddmlib.Log; 22 import com.android.ddmlib.Log.LogLevel; 23 import com.android.ddmlib.log.EventContainer; 24 import com.android.ddmlib.log.EventLogParser; 25 import com.android.ddmlib.log.LogReceiver; 26 import com.android.ddmlib.log.LogReceiver.ILogListener; 27 import com.android.ddmlib.log.LogReceiver.LogEntry; 28 import com.android.ddmuilib.DdmUiPreferences; 29 import com.android.ddmuilib.TablePanel; 30 import com.android.ddmuilib.actions.ICommonAction; 31 import com.android.ddmuilib.annotation.UiThread; 32 import com.android.ddmuilib.annotation.WorkerThread; 33 import com.android.ddmuilib.log.event.EventDisplay.ILogColumnListener; 34 35 import org.eclipse.jface.preference.IPreferenceStore; 36 import org.eclipse.swt.SWT; 37 import org.eclipse.swt.SWTException; 38 import org.eclipse.swt.custom.ScrolledComposite; 39 import org.eclipse.swt.events.ControlAdapter; 40 import org.eclipse.swt.events.ControlEvent; 41 import org.eclipse.swt.events.DisposeEvent; 42 import org.eclipse.swt.events.DisposeListener; 43 import org.eclipse.swt.graphics.Rectangle; 44 import org.eclipse.swt.layout.GridData; 45 import org.eclipse.swt.layout.RowData; 46 import org.eclipse.swt.layout.RowLayout; 47 import org.eclipse.swt.widgets.Composite; 48 import org.eclipse.swt.widgets.Control; 49 import org.eclipse.swt.widgets.Display; 50 import org.eclipse.swt.widgets.FileDialog; 51 import org.eclipse.swt.widgets.Table; 52 import org.eclipse.swt.widgets.TableColumn; 53 54 import java.io.File; 55 import java.io.FileInputStream; 56 import java.io.FileNotFoundException; 57 import java.io.FileOutputStream; 58 import java.io.IOException; 59 import java.text.NumberFormat; 60 import java.util.ArrayList; 61 import java.util.regex.Pattern; 62 63 /** 64 * Event log viewer 65 */ 66 public class EventLogPanel extends TablePanel implements ILogListener, 67 ILogColumnListener { 68 69 private final static String TAG_FILE_EXT = ".tag"; //$NON-NLS-1$ 70 71 private final static String PREFS_EVENT_DISPLAY = "EventLogPanel.eventDisplay"; //$NON-NLS-1$ 72 private final static String EVENT_DISPLAY_STORAGE_SEPARATOR = "|"; //$NON-NLS-1$ 73 74 static final String PREFS_DISPLAY_WIDTH = "EventLogPanel.width"; //$NON-NLS-1$ 75 static final String PREFS_DISPLAY_HEIGHT = "EventLogPanel.height"; //$NON-NLS-1$ 76 77 private final static int DEFAULT_DISPLAY_WIDTH = 500; 78 private final static int DEFAULT_DISPLAY_HEIGHT = 400; 79 80 private IDevice mCurrentLoggedDevice; 81 private String mCurrentLogFile; 82 private LogReceiver mCurrentLogReceiver; 83 private EventLogParser mCurrentEventLogParser; 84 85 private Object mLock = new Object(); 86 87 /** list of all the events. */ 88 private final ArrayList<EventContainer> mEvents = new ArrayList<EventContainer>(); 89 90 /** list of all the new events, that have yet to be displayed by the ui */ 91 private final ArrayList<EventContainer> mNewEvents = new ArrayList<EventContainer>(); 92 /** indicates a pending ui thread display */ 93 private boolean mPendingDisplay = false; 94 95 /** list of all the custom event displays */ 96 private final ArrayList<EventDisplay> mEventDisplays = new ArrayList<EventDisplay>(); 97 98 private final NumberFormat mFormatter = NumberFormat.getInstance(); 99 private Composite mParent; 100 private ScrolledComposite mBottomParentPanel; 101 private Composite mBottomPanel; 102 private ICommonAction mOptionsAction; 103 private ICommonAction mClearAction; 104 private ICommonAction mSaveAction; 105 private ICommonAction mLoadAction; 106 private ICommonAction mImportAction; 107 108 /** file containing the current log raw data. */ 109 private File mTempFile = null; 110 EventLogPanel()111 public EventLogPanel() { 112 super(); 113 mFormatter.setGroupingUsed(true); 114 } 115 116 /** 117 * Sets the external actions. 118 * <p/>This method sets up the {@link ICommonAction} objects to execute the proper code 119 * when triggered by using {@link ICommonAction#setRunnable(Runnable)}. 120 * <p/>It will also make sure they are enabled only when possible. 121 * @param optionsAction 122 * @param clearAction 123 * @param saveAction 124 * @param loadAction 125 * @param importAction 126 */ setActions(ICommonAction optionsAction, ICommonAction clearAction, ICommonAction saveAction, ICommonAction loadAction, ICommonAction importAction)127 public void setActions(ICommonAction optionsAction, ICommonAction clearAction, 128 ICommonAction saveAction, ICommonAction loadAction, ICommonAction importAction) { 129 mOptionsAction = optionsAction; 130 mOptionsAction.setRunnable(new Runnable() { 131 public void run() { 132 openOptionPanel(); 133 } 134 }); 135 136 mClearAction = clearAction; 137 mClearAction.setRunnable(new Runnable() { 138 public void run() { 139 clearLog(); 140 } 141 }); 142 143 mSaveAction = saveAction; 144 mSaveAction.setRunnable(new Runnable() { 145 public void run() { 146 try { 147 FileDialog fileDialog = new FileDialog(mParent.getShell(), SWT.SAVE); 148 149 fileDialog.setText("Save Event Log"); 150 fileDialog.setFileName("event.log"); 151 152 String fileName = fileDialog.open(); 153 if (fileName != null) { 154 saveLog(fileName); 155 } 156 } catch (IOException e1) { 157 } 158 } 159 }); 160 161 mLoadAction = loadAction; 162 mLoadAction.setRunnable(new Runnable() { 163 public void run() { 164 FileDialog fileDialog = new FileDialog(mParent.getShell(), SWT.OPEN); 165 166 fileDialog.setText("Load Event Log"); 167 168 String fileName = fileDialog.open(); 169 if (fileName != null) { 170 loadLog(fileName); 171 } 172 } 173 }); 174 175 mImportAction = importAction; 176 mImportAction.setRunnable(new Runnable() { 177 public void run() { 178 FileDialog fileDialog = new FileDialog(mParent.getShell(), SWT.OPEN); 179 180 fileDialog.setText("Import Bug Report"); 181 182 String fileName = fileDialog.open(); 183 if (fileName != null) { 184 importBugReport(fileName); 185 } 186 } 187 }); 188 189 mOptionsAction.setEnabled(false); 190 mClearAction.setEnabled(false); 191 mSaveAction.setEnabled(false); 192 } 193 194 /** 195 * Opens the option panel. 196 * </p> 197 * <b>This must be called from the UI thread</b> 198 */ 199 @UiThread openOptionPanel()200 public void openOptionPanel() { 201 try { 202 EventDisplayOptions dialog = new EventDisplayOptions(mParent.getShell()); 203 if (dialog.open(mCurrentEventLogParser, mEventDisplays, mEvents)) { 204 synchronized (mLock) { 205 // get the new EventDisplay list 206 mEventDisplays.clear(); 207 mEventDisplays.addAll(dialog.getEventDisplays()); 208 209 // since the list of EventDisplay changed, we store it. 210 saveEventDisplays(); 211 212 rebuildUi(); 213 } 214 } 215 } catch (SWTException e) { 216 Log.e("EventLog", e); //$NON-NLS-1$ 217 } 218 } 219 220 /** 221 * Clears the log. 222 * <p/> 223 * <b>This must be called from the UI thread</b> 224 */ clearLog()225 public void clearLog() { 226 try { 227 synchronized (mLock) { 228 mEvents.clear(); 229 mNewEvents.clear(); 230 mPendingDisplay = false; 231 for (EventDisplay eventDisplay : mEventDisplays) { 232 eventDisplay.resetUI(); 233 } 234 } 235 } catch (SWTException e) { 236 Log.e("EventLog", e); //$NON-NLS-1$ 237 } 238 } 239 240 /** 241 * Saves the content of the event log into a file. The log is saved in the same 242 * binary format than on the device. 243 * @param filePath 244 * @throws IOException 245 */ saveLog(String filePath)246 public void saveLog(String filePath) throws IOException { 247 if (mCurrentLoggedDevice != null && mCurrentEventLogParser != null) { 248 File destFile = new File(filePath); 249 destFile.createNewFile(); 250 FileInputStream fis = new FileInputStream(mTempFile); 251 FileOutputStream fos = new FileOutputStream(destFile); 252 byte[] buffer = new byte[1024]; 253 254 int count; 255 256 while ((count = fis.read(buffer)) != -1) { 257 fos.write(buffer, 0, count); 258 } 259 260 fos.close(); 261 fis.close(); 262 263 // now we save the tag file 264 filePath = filePath + TAG_FILE_EXT; 265 mCurrentEventLogParser.saveTags(filePath); 266 } 267 } 268 269 /** 270 * Loads a binary event log (if has associated .tag file) or 271 * otherwise loads a textual event log. 272 * @param filePath Event log path (and base of potential tag file) 273 */ loadLog(String filePath)274 public void loadLog(String filePath) { 275 if ((new File(filePath + TAG_FILE_EXT)).exists()) { 276 startEventLogFromFiles(filePath); 277 } else { 278 try { 279 EventLogImporter importer = new EventLogImporter(filePath); 280 String[] tags = importer.getTags(); 281 String[] log = importer.getLog(); 282 startEventLogFromContent(tags, log); 283 } catch (FileNotFoundException e) { 284 // If this fails, display the error message from startEventLogFromFiles, 285 // and pretend we never tried EventLogImporter 286 Log.logAndDisplay(Log.LogLevel.ERROR, "EventLog", 287 String.format("Failure to read %1$s", filePath + TAG_FILE_EXT)); 288 } 289 290 } 291 } 292 importBugReport(String filePath)293 public void importBugReport(String filePath) { 294 try { 295 BugReportImporter importer = new BugReportImporter(filePath); 296 297 String[] tags = importer.getTags(); 298 String[] log = importer.getLog(); 299 300 startEventLogFromContent(tags, log); 301 302 } catch (FileNotFoundException e) { 303 Log.logAndDisplay(LogLevel.ERROR, "Import", 304 "Unable to import bug report: " + e.getMessage()); 305 } 306 } 307 308 /* (non-Javadoc) 309 * @see com.android.ddmuilib.SelectionDependentPanel#clientSelected() 310 */ 311 @Override clientSelected()312 public void clientSelected() { 313 // pass 314 } 315 316 /* (non-Javadoc) 317 * @see com.android.ddmuilib.SelectionDependentPanel#deviceSelected() 318 */ 319 @Override deviceSelected()320 public void deviceSelected() { 321 startEventLog(getCurrentDevice()); 322 } 323 324 /* 325 * (non-Javadoc) 326 * @see com.android.ddmlib.AndroidDebugBridge.IClientChangeListener#clientChanged(com.android.ddmlib.Client, int) 327 */ clientChanged(Client client, int changeMask)328 public void clientChanged(Client client, int changeMask) { 329 // pass 330 } 331 332 /* (non-Javadoc) 333 * @see com.android.ddmuilib.Panel#createControl(org.eclipse.swt.widgets.Composite) 334 */ 335 @Override createControl(Composite parent)336 protected Control createControl(Composite parent) { 337 mParent = parent; 338 mParent.addDisposeListener(new DisposeListener() { 339 public void widgetDisposed(DisposeEvent e) { 340 synchronized (mLock) { 341 if (mCurrentLogReceiver != null) { 342 mCurrentLogReceiver.cancel(); 343 mCurrentLogReceiver = null; 344 mCurrentEventLogParser = null; 345 mCurrentLoggedDevice = null; 346 mEventDisplays.clear(); 347 mEvents.clear(); 348 } 349 } 350 } 351 }); 352 353 final IPreferenceStore store = DdmUiPreferences.getStore(); 354 355 // init some store stuff 356 store.setDefault(PREFS_DISPLAY_WIDTH, DEFAULT_DISPLAY_WIDTH); 357 store.setDefault(PREFS_DISPLAY_HEIGHT, DEFAULT_DISPLAY_HEIGHT); 358 359 mBottomParentPanel = new ScrolledComposite(parent, SWT.V_SCROLL); 360 mBottomParentPanel.setLayoutData(new GridData(GridData.FILL_BOTH)); 361 mBottomParentPanel.setExpandHorizontal(true); 362 mBottomParentPanel.setExpandVertical(true); 363 364 mBottomParentPanel.addControlListener(new ControlAdapter() { 365 @Override 366 public void controlResized(ControlEvent e) { 367 if (mBottomPanel != null) { 368 Rectangle r = mBottomParentPanel.getClientArea(); 369 mBottomParentPanel.setMinSize(mBottomPanel.computeSize(r.width, 370 SWT.DEFAULT)); 371 } 372 } 373 }); 374 375 prepareDisplayUi(); 376 377 // load the EventDisplay from storage. 378 loadEventDisplays(); 379 380 // create the ui 381 createDisplayUi(); 382 383 return mBottomParentPanel; 384 } 385 386 /* (non-Javadoc) 387 * @see com.android.ddmuilib.Panel#postCreation() 388 */ 389 @Override postCreation()390 protected void postCreation() { 391 // pass 392 } 393 394 /* (non-Javadoc) 395 * @see com.android.ddmuilib.Panel#setFocus() 396 */ 397 @Override setFocus()398 public void setFocus() { 399 mBottomParentPanel.setFocus(); 400 } 401 402 /** 403 * Starts a new logcat and set mCurrentLogCat as the current receiver. 404 * @param device the device to connect logcat to. 405 */ startEventLog(final IDevice device)406 private void startEventLog(final IDevice device) { 407 if (device == mCurrentLoggedDevice) { 408 return; 409 } 410 411 // if we have a logcat already running 412 if (mCurrentLogReceiver != null) { 413 stopEventLog(false); 414 } 415 mCurrentLoggedDevice = null; 416 mCurrentLogFile = null; 417 418 if (device != null) { 419 // create a new output receiver 420 mCurrentLogReceiver = new LogReceiver(this); 421 422 // start the logcat in a different thread 423 new Thread("EventLog") { //$NON-NLS-1$ 424 @Override 425 public void run() { 426 while (device.isOnline() == false && 427 mCurrentLogReceiver != null && 428 mCurrentLogReceiver.isCancelled() == false) { 429 try { 430 sleep(2000); 431 } catch (InterruptedException e) { 432 return; 433 } 434 } 435 436 if (mCurrentLogReceiver == null || mCurrentLogReceiver.isCancelled()) { 437 // logcat was stopped/cancelled before the device became ready. 438 return; 439 } 440 441 try { 442 mCurrentLoggedDevice = device; 443 synchronized (mLock) { 444 mCurrentEventLogParser = new EventLogParser(); 445 mCurrentEventLogParser.init(device); 446 } 447 448 // update the event display with the new parser. 449 updateEventDisplays(); 450 451 // prepare the temp file that will contain the raw data 452 mTempFile = File.createTempFile("android-event-", ".log"); 453 454 device.runEventLogService(mCurrentLogReceiver); 455 } catch (Exception e) { 456 Log.e("EventLog", e); 457 } finally { 458 } 459 } 460 }.start(); 461 } 462 } 463 startEventLogFromFiles(final String fileName)464 private void startEventLogFromFiles(final String fileName) { 465 // if we have a logcat already running 466 if (mCurrentLogReceiver != null) { 467 stopEventLog(false); 468 } 469 mCurrentLoggedDevice = null; 470 mCurrentLogFile = null; 471 472 // create a new output receiver 473 mCurrentLogReceiver = new LogReceiver(this); 474 475 mSaveAction.setEnabled(false); 476 477 // start the logcat in a different thread 478 new Thread("EventLog") { //$NON-NLS-1$ 479 @Override 480 public void run() { 481 try { 482 mCurrentLogFile = fileName; 483 synchronized (mLock) { 484 mCurrentEventLogParser = new EventLogParser(); 485 if (mCurrentEventLogParser.init(fileName + TAG_FILE_EXT) == false) { 486 mCurrentEventLogParser = null; 487 Log.logAndDisplay(LogLevel.ERROR, "EventLog", 488 String.format("Failure to read %1$s", fileName + TAG_FILE_EXT)); 489 return; 490 } 491 } 492 493 // update the event display with the new parser. 494 updateEventDisplays(); 495 496 runLocalEventLogService(fileName, mCurrentLogReceiver); 497 } catch (Exception e) { 498 Log.e("EventLog", e); 499 } finally { 500 } 501 } 502 }.start(); 503 } 504 startEventLogFromContent(final String[] tags, final String[] log)505 private void startEventLogFromContent(final String[] tags, final String[] log) { 506 // if we have a logcat already running 507 if (mCurrentLogReceiver != null) { 508 stopEventLog(false); 509 } 510 mCurrentLoggedDevice = null; 511 mCurrentLogFile = null; 512 513 // create a new output receiver 514 mCurrentLogReceiver = new LogReceiver(this); 515 516 mSaveAction.setEnabled(false); 517 518 // start the logcat in a different thread 519 new Thread("EventLog") { //$NON-NLS-1$ 520 @Override 521 public void run() { 522 try { 523 synchronized (mLock) { 524 mCurrentEventLogParser = new EventLogParser(); 525 if (mCurrentEventLogParser.init(tags) == false) { 526 mCurrentEventLogParser = null; 527 return; 528 } 529 } 530 531 // update the event display with the new parser. 532 updateEventDisplays(); 533 534 runLocalEventLogService(log, mCurrentLogReceiver); 535 } catch (Exception e) { 536 Log.e("EventLog", e); 537 } finally { 538 } 539 } 540 }.start(); 541 } 542 543 stopEventLog(boolean inUiThread)544 public void stopEventLog(boolean inUiThread) { 545 if (mCurrentLogReceiver != null) { 546 mCurrentLogReceiver.cancel(); 547 548 // when the thread finishes, no one will reference that object 549 // and it'll be destroyed 550 synchronized (mLock) { 551 mCurrentLogReceiver = null; 552 mCurrentEventLogParser = null; 553 554 mCurrentLoggedDevice = null; 555 mEvents.clear(); 556 mNewEvents.clear(); 557 mPendingDisplay = false; 558 } 559 560 resetUI(inUiThread); 561 } 562 563 if (mTempFile != null) { 564 mTempFile.delete(); 565 mTempFile = null; 566 } 567 } 568 resetUI(boolean inUiThread)569 private void resetUI(boolean inUiThread) { 570 mEvents.clear(); 571 572 // the ui is static we just empty it. 573 if (inUiThread) { 574 resetUiFromUiThread(); 575 } else { 576 try { 577 Display d = mBottomParentPanel.getDisplay(); 578 579 // run sync as we need to update right now. 580 d.syncExec(new Runnable() { 581 public void run() { 582 if (mBottomParentPanel.isDisposed() == false) { 583 resetUiFromUiThread(); 584 } 585 } 586 }); 587 } catch (SWTException e) { 588 // display is disposed, we're quitting. Do nothing. 589 } 590 } 591 } 592 resetUiFromUiThread()593 private void resetUiFromUiThread() { 594 synchronized (mLock) { 595 for (EventDisplay eventDisplay : mEventDisplays) { 596 eventDisplay.resetUI(); 597 } 598 } 599 mOptionsAction.setEnabled(false); 600 mClearAction.setEnabled(false); 601 mSaveAction.setEnabled(false); 602 } 603 prepareDisplayUi()604 private void prepareDisplayUi() { 605 mBottomPanel = new Composite(mBottomParentPanel, SWT.NONE); 606 mBottomParentPanel.setContent(mBottomPanel); 607 } 608 createDisplayUi()609 private void createDisplayUi() { 610 RowLayout rowLayout = new RowLayout(); 611 rowLayout.wrap = true; 612 rowLayout.pack = false; 613 rowLayout.justify = true; 614 rowLayout.fill = true; 615 rowLayout.type = SWT.HORIZONTAL; 616 mBottomPanel.setLayout(rowLayout); 617 618 IPreferenceStore store = DdmUiPreferences.getStore(); 619 int displayWidth = store.getInt(PREFS_DISPLAY_WIDTH); 620 int displayHeight = store.getInt(PREFS_DISPLAY_HEIGHT); 621 622 for (EventDisplay eventDisplay : mEventDisplays) { 623 Control c = eventDisplay.createComposite(mBottomPanel, mCurrentEventLogParser, this); 624 if (c != null) { 625 RowData rd = new RowData(); 626 rd.height = displayHeight; 627 rd.width = displayWidth; 628 c.setLayoutData(rd); 629 } 630 631 Table table = eventDisplay.getTable(); 632 if (table != null) { 633 addTableToFocusListener(table); 634 } 635 } 636 637 mBottomPanel.layout(); 638 mBottomParentPanel.setMinSize(mBottomPanel.computeSize(SWT.DEFAULT, SWT.DEFAULT)); 639 mBottomParentPanel.layout(); 640 } 641 642 /** 643 * Rebuild the display ui. 644 */ 645 @UiThread rebuildUi()646 private void rebuildUi() { 647 synchronized (mLock) { 648 // we need to rebuild the ui. First we get rid of it. 649 mBottomPanel.dispose(); 650 mBottomPanel = null; 651 652 prepareDisplayUi(); 653 createDisplayUi(); 654 655 // and fill it 656 657 boolean start_event = false; 658 synchronized (mNewEvents) { 659 mNewEvents.addAll(0, mEvents); 660 661 if (mPendingDisplay == false) { 662 mPendingDisplay = true; 663 start_event = true; 664 } 665 } 666 667 if (start_event) { 668 scheduleUIEventHandler(); 669 } 670 671 Rectangle r = mBottomParentPanel.getClientArea(); 672 mBottomParentPanel.setMinSize(mBottomPanel.computeSize(r.width, 673 SWT.DEFAULT)); 674 } 675 } 676 677 678 /** 679 * Processes a new {@link LogEntry} by parsing it with {@link EventLogParser} and displaying it. 680 * @param entry The new log entry 681 * @see LogReceiver.ILogListener#newEntry(LogEntry) 682 */ 683 @WorkerThread newEntry(LogEntry entry)684 public void newEntry(LogEntry entry) { 685 synchronized (mLock) { 686 if (mCurrentEventLogParser != null) { 687 EventContainer event = mCurrentEventLogParser.parse(entry); 688 if (event != null) { 689 handleNewEvent(event); 690 } 691 } 692 } 693 } 694 695 @WorkerThread handleNewEvent(EventContainer event)696 private void handleNewEvent(EventContainer event) { 697 // add the event to the generic list 698 mEvents.add(event); 699 700 // add to the list of events that needs to be displayed, and trigger a 701 // new display if needed. 702 boolean start_event = false; 703 synchronized (mNewEvents) { 704 mNewEvents.add(event); 705 706 if (mPendingDisplay == false) { 707 mPendingDisplay = true; 708 start_event = true; 709 } 710 } 711 712 if (start_event == false) { 713 // we're done 714 return; 715 } 716 717 scheduleUIEventHandler(); 718 } 719 720 /** 721 * Schedules the UI thread to execute a {@link Runnable} calling {@link #displayNewEvents()}. 722 */ scheduleUIEventHandler()723 private void scheduleUIEventHandler() { 724 try { 725 Display d = mBottomParentPanel.getDisplay(); 726 d.asyncExec(new Runnable() { 727 public void run() { 728 if (mBottomParentPanel.isDisposed() == false) { 729 if (mCurrentEventLogParser != null) { 730 displayNewEvents(); 731 } 732 } 733 } 734 }); 735 } catch (SWTException e) { 736 // if the ui is disposed, do nothing 737 } 738 } 739 740 /** 741 * Processes raw data coming from the log service. 742 * @see LogReceiver.ILogListener#newData(byte[], int, int) 743 */ newData(byte[] data, int offset, int length)744 public void newData(byte[] data, int offset, int length) { 745 if (mTempFile != null) { 746 try { 747 FileOutputStream fos = new FileOutputStream(mTempFile, true /* append */); 748 fos.write(data, offset, length); 749 fos.close(); 750 } catch (FileNotFoundException e) { 751 } catch (IOException e) { 752 } 753 } 754 } 755 756 @UiThread displayNewEvents()757 private void displayNewEvents() { 758 // never display more than 1,000 events in this loop. We can't do too much in the UI thread. 759 int count = 0; 760 761 // prepare the displays 762 for (EventDisplay eventDisplay : mEventDisplays) { 763 eventDisplay.startMultiEventDisplay(); 764 } 765 766 // display the new events 767 EventContainer event = null; 768 boolean need_to_reloop = false; 769 do { 770 // get the next event to display. 771 synchronized (mNewEvents) { 772 if (mNewEvents.size() > 0) { 773 if (count > 200) { 774 // there are still events to be displayed, but we don't want to hog the 775 // UI thread for too long, so we stop this runnable, but launch a new 776 // one to keep going. 777 need_to_reloop = true; 778 event = null; 779 } else { 780 event = mNewEvents.remove(0); 781 count++; 782 } 783 } else { 784 // we're done. 785 event = null; 786 mPendingDisplay = false; 787 } 788 } 789 790 if (event != null) { 791 // notify the event display 792 for (EventDisplay eventDisplay : mEventDisplays) { 793 eventDisplay.newEvent(event, mCurrentEventLogParser); 794 } 795 } 796 } while (event != null); 797 798 // we're done displaying events. 799 for (EventDisplay eventDisplay : mEventDisplays) { 800 eventDisplay.endMultiEventDisplay(); 801 } 802 803 // if needed, ask the UI thread to re-run this method. 804 if (need_to_reloop) { 805 scheduleUIEventHandler(); 806 } 807 } 808 809 /** 810 * Loads the {@link EventDisplay}s from the preference store. 811 */ loadEventDisplays()812 private void loadEventDisplays() { 813 IPreferenceStore store = DdmUiPreferences.getStore(); 814 String storage = store.getString(PREFS_EVENT_DISPLAY); 815 816 if (storage.length() > 0) { 817 String[] values = storage.split(Pattern.quote(EVENT_DISPLAY_STORAGE_SEPARATOR)); 818 819 for (String value : values) { 820 EventDisplay eventDisplay = EventDisplay.load(value); 821 if (eventDisplay != null) { 822 mEventDisplays.add(eventDisplay); 823 } 824 } 825 } 826 } 827 828 /** 829 * Saves the {@link EventDisplay}s into the {@link DdmUiPreferences} store. 830 */ saveEventDisplays()831 private void saveEventDisplays() { 832 IPreferenceStore store = DdmUiPreferences.getStore(); 833 834 boolean first = true; 835 StringBuilder sb = new StringBuilder(); 836 837 for (EventDisplay eventDisplay : mEventDisplays) { 838 String storage = eventDisplay.getStorageString(); 839 if (storage != null) { 840 if (first == false) { 841 sb.append(EVENT_DISPLAY_STORAGE_SEPARATOR); 842 } else { 843 first = false; 844 } 845 846 sb.append(storage); 847 } 848 } 849 850 store.setValue(PREFS_EVENT_DISPLAY, sb.toString()); 851 } 852 853 /** 854 * Updates the {@link EventDisplay} with the new {@link EventLogParser}. 855 * <p/> 856 * This will run asynchronously in the UI thread. 857 */ 858 @WorkerThread updateEventDisplays()859 private void updateEventDisplays() { 860 try { 861 Display d = mBottomParentPanel.getDisplay(); 862 863 d.asyncExec(new Runnable() { 864 public void run() { 865 if (mBottomParentPanel.isDisposed() == false) { 866 for (EventDisplay eventDisplay : mEventDisplays) { 867 eventDisplay.setNewLogParser(mCurrentEventLogParser); 868 } 869 870 mOptionsAction.setEnabled(true); 871 mClearAction.setEnabled(true); 872 if (mCurrentLogFile == null) { 873 mSaveAction.setEnabled(true); 874 } else { 875 mSaveAction.setEnabled(false); 876 } 877 } 878 } 879 }); 880 } catch (SWTException e) { 881 // display is disposed: do nothing. 882 } 883 } 884 885 @UiThread columnResized(int index, TableColumn sourceColumn)886 public void columnResized(int index, TableColumn sourceColumn) { 887 for (EventDisplay eventDisplay : mEventDisplays) { 888 eventDisplay.resizeColumn(index, sourceColumn); 889 } 890 } 891 892 /** 893 * Runs an event log service out of a local file. 894 * @param fileName the full file name of the local file containing the event log. 895 * @param logReceiver the receiver that will handle the log 896 * @throws IOException 897 */ 898 @WorkerThread runLocalEventLogService(String fileName, LogReceiver logReceiver)899 private void runLocalEventLogService(String fileName, LogReceiver logReceiver) 900 throws IOException { 901 byte[] buffer = new byte[256]; 902 903 FileInputStream fis = new FileInputStream(fileName); 904 905 int count; 906 while ((count = fis.read(buffer)) != -1) { 907 logReceiver.parseNewData(buffer, 0, count); 908 } 909 } 910 911 @WorkerThread runLocalEventLogService(String[] log, LogReceiver currentLogReceiver)912 private void runLocalEventLogService(String[] log, LogReceiver currentLogReceiver) { 913 synchronized (mLock) { 914 for (String line : log) { 915 EventContainer event = mCurrentEventLogParser.parse(line); 916 if (event != null) { 917 handleNewEvent(event); 918 } 919 } 920 } 921 } 922 } 923