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.Log; 20 import com.android.ddmlib.log.EventContainer; 21 import com.android.ddmlib.log.EventContainer.CompareMethod; 22 import com.android.ddmlib.log.EventContainer.EventValueType; 23 import com.android.ddmlib.log.EventLogParser; 24 import com.android.ddmlib.log.EventValueDescription.ValueType; 25 import com.android.ddmlib.log.InvalidTypeException; 26 import org.eclipse.swt.SWT; 27 import org.eclipse.swt.events.DisposeEvent; 28 import org.eclipse.swt.events.DisposeListener; 29 import org.eclipse.swt.graphics.Font; 30 import org.eclipse.swt.graphics.FontData; 31 import org.eclipse.swt.widgets.Composite; 32 import org.eclipse.swt.widgets.Control; 33 import org.eclipse.swt.widgets.Table; 34 import org.eclipse.swt.widgets.TableColumn; 35 import org.jfree.chart.ChartFactory; 36 import org.jfree.chart.JFreeChart; 37 import org.jfree.chart.event.ChartChangeEvent; 38 import org.jfree.chart.event.ChartChangeEventType; 39 import org.jfree.chart.event.ChartChangeListener; 40 import org.jfree.chart.plot.XYPlot; 41 import org.jfree.chart.title.TextTitle; 42 import org.jfree.data.time.Millisecond; 43 import org.jfree.data.time.TimeSeries; 44 import org.jfree.data.time.TimeSeriesCollection; 45 import org.jfree.experimental.chart.swt.ChartComposite; 46 import org.jfree.experimental.swt.SWTUtils; 47 48 import java.security.InvalidParameterException; 49 import java.util.ArrayList; 50 import java.util.Date; 51 import java.util.HashMap; 52 import java.util.Iterator; 53 import java.util.Set; 54 import java.util.regex.Pattern; 55 56 /** 57 * Represents a custom display of one or more events. 58 */ 59 abstract class EventDisplay { 60 61 private final static String DISPLAY_DATA_STORAGE_SEPARATOR = ":"; //$NON-NLS-1$ 62 private final static String PID_STORAGE_SEPARATOR = ","; //$NON-NLS-1$ 63 private final static String DESCRIPTOR_STORAGE_SEPARATOR = "$"; //$NON-NLS-1$ 64 private final static String DESCRIPTOR_DATA_STORAGE_SEPARATOR = "!"; //$NON-NLS-1$ 65 66 private final static String FILTER_VALUE_NULL = "<null>"; //$NON-NLS-1$ 67 68 public final static int DISPLAY_TYPE_LOG_ALL = 0; 69 public final static int DISPLAY_TYPE_FILTERED_LOG = 1; 70 public final static int DISPLAY_TYPE_GRAPH = 2; 71 public final static int DISPLAY_TYPE_SYNC = 3; 72 public final static int DISPLAY_TYPE_SYNC_HIST = 4; 73 public final static int DISPLAY_TYPE_SYNC_PERF = 5; 74 75 private final static int EVENT_CHECK_FAILED = 0; 76 protected final static int EVENT_CHECK_SAME_TAG = 1; 77 protected final static int EVENT_CHECK_SAME_VALUE = 2; 78 79 /** 80 * Creates the appropriate EventDisplay subclass. 81 * 82 * @param type the type of display (DISPLAY_TYPE_LOG_ALL, etc) 83 * @param name the name of the display 84 * @return the created object 85 */ 86 public static EventDisplay eventDisplayFactory(int type, String name) { 87 switch (type) { 88 case DISPLAY_TYPE_LOG_ALL: 89 return new DisplayLog(name); 90 case DISPLAY_TYPE_FILTERED_LOG: 91 return new DisplayFilteredLog(name); 92 case DISPLAY_TYPE_SYNC: 93 return new DisplaySync(name); 94 case DISPLAY_TYPE_SYNC_HIST: 95 return new DisplaySyncHistogram(name); 96 case DISPLAY_TYPE_GRAPH: 97 return new DisplayGraph(name); 98 case DISPLAY_TYPE_SYNC_PERF: 99 return new DisplaySyncPerf(name); 100 default: 101 throw new InvalidParameterException("Unknown Display Type " + type); //$NON-NLS-1$ 102 } 103 } 104 105 /** 106 * Adds event to the display. 107 * @param event The event 108 * @param logParser The log parser. 109 */ 110 abstract void newEvent(EventContainer event, EventLogParser logParser); 111 112 /** 113 * Resets the display. 114 */ 115 abstract void resetUI(); 116 117 /** 118 * Gets display type 119 * 120 * @return display type as an integer 121 */ 122 abstract int getDisplayType(); 123 124 /** 125 * Creates the UI for the event display. 126 * 127 * @param parent the parent composite. 128 * @param logParser the current log parser. 129 * @return the created control (which may have children). 130 */ 131 abstract Control createComposite(final Composite parent, EventLogParser logParser, 132 final ILogColumnListener listener); 133 134 interface ILogColumnListener { 135 void columnResized(int index, TableColumn sourceColumn); 136 } 137 138 /** 139 * Describes an event to be displayed. 140 */ 141 static class OccurrenceDisplayDescriptor { 142 143 int eventTag = -1; 144 int seriesValueIndex = -1; 145 boolean includePid = false; 146 int filterValueIndex = -1; 147 CompareMethod filterCompareMethod = CompareMethod.EQUAL_TO; 148 Object filterValue = null; 149 150 OccurrenceDisplayDescriptor() { 151 } 152 153 OccurrenceDisplayDescriptor(OccurrenceDisplayDescriptor descriptor) { 154 replaceWith(descriptor); 155 } 156 157 OccurrenceDisplayDescriptor(int eventTag) { 158 this.eventTag = eventTag; 159 } 160 161 OccurrenceDisplayDescriptor(int eventTag, int seriesValueIndex) { 162 this.eventTag = eventTag; 163 this.seriesValueIndex = seriesValueIndex; 164 } 165 166 void replaceWith(OccurrenceDisplayDescriptor descriptor) { 167 eventTag = descriptor.eventTag; 168 seriesValueIndex = descriptor.seriesValueIndex; 169 includePid = descriptor.includePid; 170 filterValueIndex = descriptor.filterValueIndex; 171 filterCompareMethod = descriptor.filterCompareMethod; 172 filterValue = descriptor.filterValue; 173 } 174 175 /** 176 * Loads the descriptor parameter from a storage string. The storage string must have 177 * been generated with {@link #getStorageString()}. 178 * 179 * @param storageString the storage string 180 */ 181 final void loadFrom(String storageString) { 182 String[] values = storageString.split(Pattern.quote(DESCRIPTOR_DATA_STORAGE_SEPARATOR)); 183 loadFrom(values, 0); 184 } 185 186 /** 187 * Loads the parameters from an array of strings. 188 * 189 * @param storageStrings the strings representing each parameter. 190 * @param index the starting index in the array of strings. 191 * @return the new index in the array. 192 */ 193 protected int loadFrom(String[] storageStrings, int index) { 194 eventTag = Integer.parseInt(storageStrings[index++]); 195 seriesValueIndex = Integer.parseInt(storageStrings[index++]); 196 includePid = Boolean.parseBoolean(storageStrings[index++]); 197 filterValueIndex = Integer.parseInt(storageStrings[index++]); 198 try { 199 filterCompareMethod = CompareMethod.valueOf(storageStrings[index++]); 200 } catch (IllegalArgumentException e) { 201 // if the name does not match any known CompareMethod, we init it to the default one 202 filterCompareMethod = CompareMethod.EQUAL_TO; 203 } 204 String value = storageStrings[index++]; 205 if (filterValueIndex != -1 && FILTER_VALUE_NULL.equals(value) == false) { 206 filterValue = EventValueType.getObjectFromStorageString(value); 207 } 208 209 return index; 210 } 211 212 /** 213 * Returns the storage string for the receiver. 214 */ 215 String getStorageString() { 216 StringBuilder sb = new StringBuilder(); 217 sb.append(eventTag); 218 sb.append(DESCRIPTOR_DATA_STORAGE_SEPARATOR); 219 sb.append(seriesValueIndex); 220 sb.append(DESCRIPTOR_DATA_STORAGE_SEPARATOR); 221 sb.append(Boolean.toString(includePid)); 222 sb.append(DESCRIPTOR_DATA_STORAGE_SEPARATOR); 223 sb.append(filterValueIndex); 224 sb.append(DESCRIPTOR_DATA_STORAGE_SEPARATOR); 225 sb.append(filterCompareMethod.name()); 226 sb.append(DESCRIPTOR_DATA_STORAGE_SEPARATOR); 227 if (filterValue != null) { 228 String value = EventValueType.getStorageString(filterValue); 229 if (value != null) { 230 sb.append(value); 231 } else { 232 sb.append(FILTER_VALUE_NULL); 233 } 234 } else { 235 sb.append(FILTER_VALUE_NULL); 236 } 237 238 return sb.toString(); 239 } 240 } 241 242 /** 243 * Describes an event value to be displayed. 244 */ 245 static final class ValueDisplayDescriptor extends OccurrenceDisplayDescriptor { 246 String valueName; 247 int valueIndex = -1; 248 249 ValueDisplayDescriptor() { 250 super(); 251 } 252 253 ValueDisplayDescriptor(ValueDisplayDescriptor descriptor) { 254 super(); 255 replaceWith(descriptor); 256 } 257 258 ValueDisplayDescriptor(int eventTag, String valueName, int valueIndex) { 259 super(eventTag); 260 this.valueName = valueName; 261 this.valueIndex = valueIndex; 262 } 263 264 ValueDisplayDescriptor(int eventTag, String valueName, int valueIndex, 265 int seriesValueIndex) { 266 super(eventTag, seriesValueIndex); 267 this.valueName = valueName; 268 this.valueIndex = valueIndex; 269 } 270 271 @Override 272 void replaceWith(OccurrenceDisplayDescriptor descriptor) { 273 super.replaceWith(descriptor); 274 if (descriptor instanceof ValueDisplayDescriptor) { 275 ValueDisplayDescriptor valueDescriptor = (ValueDisplayDescriptor) descriptor; 276 valueName = valueDescriptor.valueName; 277 valueIndex = valueDescriptor.valueIndex; 278 } 279 } 280 281 /** 282 * Loads the parameters from an array of strings. 283 * 284 * @param storageStrings the strings representing each parameter. 285 * @param index the starting index in the array of strings. 286 * @return the new index in the array. 287 */ 288 @Override 289 protected int loadFrom(String[] storageStrings, int index) { 290 index = super.loadFrom(storageStrings, index); 291 valueName = storageStrings[index++]; 292 valueIndex = Integer.parseInt(storageStrings[index++]); 293 return index; 294 } 295 296 /** 297 * Returns the storage string for the receiver. 298 */ 299 @Override 300 String getStorageString() { 301 String superStorage = super.getStorageString(); 302 303 StringBuilder sb = new StringBuilder(); 304 sb.append(superStorage); 305 sb.append(DESCRIPTOR_DATA_STORAGE_SEPARATOR); 306 sb.append(valueName); 307 sb.append(DESCRIPTOR_DATA_STORAGE_SEPARATOR); 308 sb.append(valueIndex); 309 310 return sb.toString(); 311 } 312 } 313 314 /* ================== 315 * Event Display parameters. 316 * ================== */ 317 protected String mName; 318 319 private boolean mPidFiltering = false; 320 321 private ArrayList<Integer> mPidFilterList = null; 322 323 protected final ArrayList<ValueDisplayDescriptor> mValueDescriptors = 324 new ArrayList<ValueDisplayDescriptor>(); 325 private final ArrayList<OccurrenceDisplayDescriptor> mOccurrenceDescriptors = 326 new ArrayList<OccurrenceDisplayDescriptor>(); 327 328 /* ================== 329 * Event Display members for display purpose. 330 * ================== */ 331 // chart objects 332 /** 333 * This is a map of (descriptor, map2) where map2 is a map of (pid, chart-series) 334 */ 335 protected final HashMap<ValueDisplayDescriptor, HashMap<Integer, TimeSeries>> mValueDescriptorSeriesMap = 336 new HashMap<ValueDisplayDescriptor, HashMap<Integer, TimeSeries>>(); 337 /** 338 * This is a map of (descriptor, map2) where map2 is a map of (pid, chart-series) 339 */ 340 protected final HashMap<OccurrenceDisplayDescriptor, HashMap<Integer, TimeSeries>> mOcurrenceDescriptorSeriesMap = 341 new HashMap<OccurrenceDisplayDescriptor, HashMap<Integer, TimeSeries>>(); 342 343 /** 344 * This is a map of (ValueType, dataset) 345 */ 346 protected final HashMap<ValueType, TimeSeriesCollection> mValueTypeDataSetMap = 347 new HashMap<ValueType, TimeSeriesCollection>(); 348 349 protected JFreeChart mChart; 350 protected TimeSeriesCollection mOccurrenceDataSet; 351 protected int mDataSetCount; 352 private ChartComposite mChartComposite; 353 protected long mMaximumChartItemAge = -1; 354 protected long mHistWidth = 1; 355 356 // log objects. 357 protected Table mLogTable; 358 359 /* ================== 360 * Misc data. 361 * ================== */ 362 protected int mValueDescriptorCheck = EVENT_CHECK_FAILED; 363 364 EventDisplay(String name) { 365 mName = name; 366 } 367 368 static EventDisplay clone(EventDisplay from) { 369 EventDisplay ed = eventDisplayFactory(from.getDisplayType(), from.getName()); 370 ed.mName = from.mName; 371 ed.mPidFiltering = from.mPidFiltering; 372 ed.mMaximumChartItemAge = from.mMaximumChartItemAge; 373 ed.mHistWidth = from.mHistWidth; 374 375 if (from.mPidFilterList != null) { 376 ed.mPidFilterList = new ArrayList<Integer>(); 377 ed.mPidFilterList.addAll(from.mPidFilterList); 378 } 379 380 for (ValueDisplayDescriptor desc : from.mValueDescriptors) { 381 ed.mValueDescriptors.add(new ValueDisplayDescriptor(desc)); 382 } 383 ed.mValueDescriptorCheck = from.mValueDescriptorCheck; 384 385 for (OccurrenceDisplayDescriptor desc : from.mOccurrenceDescriptors) { 386 ed.mOccurrenceDescriptors.add(new OccurrenceDisplayDescriptor(desc)); 387 } 388 return ed; 389 } 390 391 /** 392 * Returns the parameters of the receiver as a single String for storage. 393 */ 394 String getStorageString() { 395 StringBuilder sb = new StringBuilder(); 396 397 sb.append(mName); 398 sb.append(DISPLAY_DATA_STORAGE_SEPARATOR); 399 sb.append(getDisplayType()); 400 sb.append(DISPLAY_DATA_STORAGE_SEPARATOR); 401 sb.append(Boolean.toString(mPidFiltering)); 402 sb.append(DISPLAY_DATA_STORAGE_SEPARATOR); 403 sb.append(getPidStorageString()); 404 sb.append(DISPLAY_DATA_STORAGE_SEPARATOR); 405 sb.append(getDescriptorStorageString(mValueDescriptors)); 406 sb.append(DISPLAY_DATA_STORAGE_SEPARATOR); 407 sb.append(getDescriptorStorageString(mOccurrenceDescriptors)); 408 sb.append(DISPLAY_DATA_STORAGE_SEPARATOR); 409 sb.append(mMaximumChartItemAge); 410 sb.append(DISPLAY_DATA_STORAGE_SEPARATOR); 411 sb.append(mHistWidth); 412 sb.append(DISPLAY_DATA_STORAGE_SEPARATOR); 413 414 return sb.toString(); 415 } 416 417 void setName(String name) { 418 mName = name; 419 } 420 421 String getName() { 422 return mName; 423 } 424 425 void setPidFiltering(boolean filterByPid) { 426 mPidFiltering = filterByPid; 427 } 428 429 boolean getPidFiltering() { 430 return mPidFiltering; 431 } 432 433 void setPidFilterList(ArrayList<Integer> pids) { 434 if (mPidFiltering == false) { 435 new InvalidParameterException(); 436 } 437 438 mPidFilterList = pids; 439 } 440 441 ArrayList<Integer> getPidFilterList() { 442 return mPidFilterList; 443 } 444 445 void addPidFiler(int pid) { 446 if (mPidFiltering == false) { 447 new InvalidParameterException(); 448 } 449 450 if (mPidFilterList == null) { 451 mPidFilterList = new ArrayList<Integer>(); 452 } 453 454 mPidFilterList.add(pid); 455 } 456 457 /** 458 * Returns an iterator to the list of {@link ValueDisplayDescriptor}. 459 */ 460 Iterator<ValueDisplayDescriptor> getValueDescriptors() { 461 return mValueDescriptors.iterator(); 462 } 463 464 /** 465 * Update checks on the descriptors. Must be called whenever a descriptor is modified outside 466 * of this class. 467 */ 468 void updateValueDescriptorCheck() { 469 mValueDescriptorCheck = checkDescriptors(); 470 } 471 472 /** 473 * Returns an iterator to the list of {@link OccurrenceDisplayDescriptor}. 474 */ 475 Iterator<OccurrenceDisplayDescriptor> getOccurrenceDescriptors() { 476 return mOccurrenceDescriptors.iterator(); 477 } 478 479 /** 480 * Adds a descriptor. This can be a {@link OccurrenceDisplayDescriptor} or a 481 * {@link ValueDisplayDescriptor}. 482 * 483 * @param descriptor the descriptor to be added. 484 */ 485 void addDescriptor(OccurrenceDisplayDescriptor descriptor) { 486 if (descriptor instanceof ValueDisplayDescriptor) { 487 mValueDescriptors.add((ValueDisplayDescriptor) descriptor); 488 mValueDescriptorCheck = checkDescriptors(); 489 } else { 490 mOccurrenceDescriptors.add(descriptor); 491 } 492 } 493 494 /** 495 * Returns a descriptor by index and class (extending {@link OccurrenceDisplayDescriptor}). 496 * 497 * @param descriptorClass the class of the descriptor to return. 498 * @param index the index of the descriptor to return. 499 * @return either a {@link OccurrenceDisplayDescriptor} or a {@link ValueDisplayDescriptor} 500 * or <code>null</code> if <code>descriptorClass</code> is another class. 501 */ 502 OccurrenceDisplayDescriptor getDescriptor( 503 Class<? extends OccurrenceDisplayDescriptor> descriptorClass, int index) { 504 505 if (descriptorClass == OccurrenceDisplayDescriptor.class) { 506 return mOccurrenceDescriptors.get(index); 507 } else if (descriptorClass == ValueDisplayDescriptor.class) { 508 return mValueDescriptors.get(index); 509 } 510 511 return null; 512 } 513 514 /** 515 * Removes a descriptor based on its class and index. 516 * 517 * @param descriptorClass the class of the descriptor. 518 * @param index the index of the descriptor to be removed. 519 */ 520 void removeDescriptor(Class<? extends OccurrenceDisplayDescriptor> descriptorClass, int index) { 521 if (descriptorClass == OccurrenceDisplayDescriptor.class) { 522 mOccurrenceDescriptors.remove(index); 523 } else if (descriptorClass == ValueDisplayDescriptor.class) { 524 mValueDescriptors.remove(index); 525 mValueDescriptorCheck = checkDescriptors(); 526 } 527 } 528 529 Control createCompositeChart(final Composite parent, EventLogParser logParser, 530 String title) { 531 mChart = ChartFactory.createTimeSeriesChart( 532 null, 533 null /* timeAxisLabel */, 534 null /* valueAxisLabel */, 535 null, /* dataset. set below */ 536 true /* legend */, 537 false /* tooltips */, 538 false /* urls */); 539 540 // get the font to make a proper title. We need to convert the swt font, 541 // into an awt font. 542 Font f = parent.getFont(); 543 FontData[] fData = f.getFontData(); 544 545 // event though on Mac OS there could be more than one fontData, we'll only use 546 // the first one. 547 FontData firstFontData = fData[0]; 548 549 java.awt.Font awtFont = SWTUtils.toAwtFont(parent.getDisplay(), 550 firstFontData, true /* ensureSameSize */); 551 552 553 mChart.setTitle(new TextTitle(title, awtFont)); 554 555 final XYPlot xyPlot = mChart.getXYPlot(); 556 xyPlot.setRangeCrosshairVisible(true); 557 xyPlot.setRangeCrosshairLockedOnData(true); 558 xyPlot.setDomainCrosshairVisible(true); 559 xyPlot.setDomainCrosshairLockedOnData(true); 560 561 mChart.addChangeListener(new ChartChangeListener() { 562 public void chartChanged(ChartChangeEvent event) { 563 ChartChangeEventType type = event.getType(); 564 if (type == ChartChangeEventType.GENERAL) { 565 // because the value we need (rangeCrosshair and domainCrosshair) are 566 // updated on the draw, but the notification happens before the draw, 567 // we process the click in a future runnable! 568 parent.getDisplay().asyncExec(new Runnable() { 569 public void run() { 570 processClick(xyPlot); 571 } 572 }); 573 } 574 } 575 }); 576 577 mChartComposite = new ChartComposite(parent, SWT.BORDER, mChart, 578 ChartComposite.DEFAULT_WIDTH, 579 ChartComposite.DEFAULT_HEIGHT, 580 ChartComposite.DEFAULT_MINIMUM_DRAW_WIDTH, 581 ChartComposite.DEFAULT_MINIMUM_DRAW_HEIGHT, 582 3000, // max draw width. We don't want it to zoom, so we put a big number 583 3000, // max draw height. We don't want it to zoom, so we put a big number 584 true, // off-screen buffer 585 true, // properties 586 true, // save 587 true, // print 588 true, // zoom 589 true); // tooltips 590 591 mChartComposite.addDisposeListener(new DisposeListener() { 592 public void widgetDisposed(DisposeEvent e) { 593 mValueTypeDataSetMap.clear(); 594 mDataSetCount = 0; 595 mOccurrenceDataSet = null; 596 mChart = null; 597 mChartComposite = null; 598 mValueDescriptorSeriesMap.clear(); 599 mOcurrenceDescriptorSeriesMap.clear(); 600 } 601 }); 602 603 return mChartComposite; 604 605 } 606 607 private void processClick(XYPlot xyPlot) { 608 double rangeValue = xyPlot.getRangeCrosshairValue(); 609 if (rangeValue != 0) { 610 double domainValue = xyPlot.getDomainCrosshairValue(); 611 612 Millisecond msec = new Millisecond(new Date((long) domainValue)); 613 614 // look for values in the dataset that contains data at this TimePeriod 615 Set<ValueDisplayDescriptor> descKeys = mValueDescriptorSeriesMap.keySet(); 616 617 for (ValueDisplayDescriptor descKey : descKeys) { 618 HashMap<Integer, TimeSeries> map = mValueDescriptorSeriesMap.get(descKey); 619 620 Set<Integer> pidKeys = map.keySet(); 621 622 for (Integer pidKey : pidKeys) { 623 TimeSeries series = map.get(pidKey); 624 625 Number value = series.getValue(msec); 626 if (value != null) { 627 // found a match. lets check against the actual value. 628 if (value.doubleValue() == rangeValue) { 629 630 return; 631 } 632 } 633 } 634 } 635 } 636 } 637 638 639 /** 640 * Resizes the <code>index</code>-th column of the log {@link Table} (if applicable). 641 * Subclasses can override if necessary. 642 * <p/> 643 * This does nothing if the <code>Table</code> object is <code>null</code> (because the display 644 * type does not use a column) or if the <code>index</code>-th column is in fact the originating 645 * column passed as argument. 646 * 647 * @param index the index of the column to resize 648 * @param sourceColumn the original column that was resize, and on which we need to sync the 649 * index-th column width. 650 */ 651 void resizeColumn(int index, TableColumn sourceColumn) { 652 } 653 654 /** 655 * Sets the current {@link EventLogParser} object. 656 * Subclasses can override if necessary. 657 */ 658 protected void setNewLogParser(EventLogParser logParser) { 659 } 660 661 /** 662 * Prepares the {@link EventDisplay} for a multi event display. 663 */ 664 void startMultiEventDisplay() { 665 if (mLogTable != null) { 666 mLogTable.setRedraw(false); 667 } 668 } 669 670 /** 671 * Finalizes the {@link EventDisplay} after a multi event display. 672 */ 673 void endMultiEventDisplay() { 674 if (mLogTable != null) { 675 mLogTable.setRedraw(true); 676 } 677 } 678 679 /** 680 * Returns the {@link Table} object used to display events, if any. 681 * 682 * @return a Table object or <code>null</code>. 683 */ 684 Table getTable() { 685 return mLogTable; 686 } 687 688 /** 689 * Loads a new {@link EventDisplay} from a storage string. The string must have been created 690 * with {@link #getStorageString()}. 691 * 692 * @param storageString the storage string 693 * @return a new {@link EventDisplay} or null if the load failed. 694 */ 695 static EventDisplay load(String storageString) { 696 if (storageString.length() > 0) { 697 // the storage string is separated by ':' 698 String[] values = storageString.split(Pattern.quote(DISPLAY_DATA_STORAGE_SEPARATOR)); 699 700 try { 701 int index = 0; 702 703 String name = values[index++]; 704 int displayType = Integer.parseInt(values[index++]); 705 boolean pidFiltering = Boolean.parseBoolean(values[index++]); 706 707 EventDisplay ed = eventDisplayFactory(displayType, name); 708 ed.setPidFiltering(pidFiltering); 709 710 // because empty sections are removed by String.split(), we have to check 711 // the index for those. 712 if (index < values.length) { 713 ed.loadPidFilters(values[index++]); 714 } 715 716 if (index < values.length) { 717 ed.loadValueDescriptors(values[index++]); 718 } 719 720 if (index < values.length) { 721 ed.loadOccurrenceDescriptors(values[index++]); 722 } 723 724 ed.updateValueDescriptorCheck(); 725 726 if (index < values.length) { 727 ed.mMaximumChartItemAge = Long.parseLong(values[index++]); 728 } 729 730 if (index < values.length) { 731 ed.mHistWidth = Long.parseLong(values[index++]); 732 } 733 734 return ed; 735 } catch (RuntimeException re) { 736 // we'll return null below. 737 Log.e("ddms", re); 738 } 739 } 740 741 return null; 742 } 743 744 private String getPidStorageString() { 745 if (mPidFilterList != null) { 746 StringBuilder sb = new StringBuilder(); 747 boolean first = true; 748 for (Integer i : mPidFilterList) { 749 if (first == false) { 750 sb.append(PID_STORAGE_SEPARATOR); 751 } else { 752 first = false; 753 } 754 sb.append(i); 755 } 756 757 return sb.toString(); 758 } 759 return ""; //$NON-NLS-1$ 760 } 761 762 763 private void loadPidFilters(String storageString) { 764 if (storageString.length() > 0) { 765 String[] values = storageString.split(Pattern.quote(PID_STORAGE_SEPARATOR)); 766 767 for (String value : values) { 768 if (mPidFilterList == null) { 769 mPidFilterList = new ArrayList<Integer>(); 770 } 771 mPidFilterList.add(Integer.parseInt(value)); 772 } 773 } 774 } 775 776 private String getDescriptorStorageString( 777 ArrayList<? extends OccurrenceDisplayDescriptor> descriptorList) { 778 StringBuilder sb = new StringBuilder(); 779 boolean first = true; 780 781 for (OccurrenceDisplayDescriptor descriptor : descriptorList) { 782 if (first == false) { 783 sb.append(DESCRIPTOR_STORAGE_SEPARATOR); 784 } else { 785 first = false; 786 } 787 sb.append(descriptor.getStorageString()); 788 } 789 790 return sb.toString(); 791 } 792 793 private void loadOccurrenceDescriptors(String storageString) { 794 if (storageString.length() == 0) { 795 return; 796 } 797 798 String[] values = storageString.split(Pattern.quote(DESCRIPTOR_STORAGE_SEPARATOR)); 799 800 for (String value : values) { 801 OccurrenceDisplayDescriptor desc = new OccurrenceDisplayDescriptor(); 802 desc.loadFrom(value); 803 mOccurrenceDescriptors.add(desc); 804 } 805 } 806 807 private void loadValueDescriptors(String storageString) { 808 if (storageString.length() == 0) { 809 return; 810 } 811 812 String[] values = storageString.split(Pattern.quote(DESCRIPTOR_STORAGE_SEPARATOR)); 813 814 for (String value : values) { 815 ValueDisplayDescriptor desc = new ValueDisplayDescriptor(); 816 desc.loadFrom(value); 817 mValueDescriptors.add(desc); 818 } 819 } 820 821 /** 822 * Fills a list with {@link OccurrenceDisplayDescriptor} (or a subclass of it) from another 823 * list if they are configured to display the {@link EventContainer} 824 * 825 * @param event the event container 826 * @param fullList the list with all the descriptors. 827 * @param outList the list to fill. 828 */ 829 @SuppressWarnings("unchecked") 830 private void getDescriptors(EventContainer event, 831 ArrayList<? extends OccurrenceDisplayDescriptor> fullList, 832 ArrayList outList) { 833 for (OccurrenceDisplayDescriptor descriptor : fullList) { 834 try { 835 // first check the event tag. 836 if (descriptor.eventTag == event.mTag) { 837 // now check if we have a filter on a value 838 if (descriptor.filterValueIndex == -1 || 839 event.testValue(descriptor.filterValueIndex, descriptor.filterValue, 840 descriptor.filterCompareMethod)) { 841 outList.add(descriptor); 842 } 843 } 844 } catch (InvalidTypeException ite) { 845 // if the filter for the descriptor was incorrect, we ignore the descriptor. 846 } catch (ArrayIndexOutOfBoundsException aioobe) { 847 // if the index was wrong (the event content may have changed since we setup the 848 // display), we do nothing but log the error 849 Log.e("Event Log", String.format( 850 "ArrayIndexOutOfBoundsException occured when checking %1$d-th value of event %2$d", //$NON-NLS-1$ 851 descriptor.filterValueIndex, descriptor.eventTag)); 852 } 853 } 854 } 855 856 /** 857 * Filters the {@link com.android.ddmlib.log.EventContainer}, and fills two list of {@link com.android.ddmuilib.log.event.EventDisplay.ValueDisplayDescriptor} 858 * and {@link com.android.ddmuilib.log.event.EventDisplay.OccurrenceDisplayDescriptor} configured to display the event. 859 * 860 * @param event 861 * @param valueDescriptors 862 * @param occurrenceDescriptors 863 * @return true if the event should be displayed. 864 */ 865 866 protected boolean filterEvent(EventContainer event, 867 ArrayList<ValueDisplayDescriptor> valueDescriptors, 868 ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors) { 869 870 // test the pid first (if needed) 871 if (mPidFiltering && mPidFilterList != null) { 872 boolean found = false; 873 for (int pid : mPidFilterList) { 874 if (pid == event.pid) { 875 found = true; 876 break; 877 } 878 } 879 880 if (found == false) { 881 return false; 882 } 883 } 884 885 // now get the list of matching descriptors 886 getDescriptors(event, mValueDescriptors, valueDescriptors); 887 getDescriptors(event, mOccurrenceDescriptors, occurrenceDescriptors); 888 889 // and return whether there is at least one match in either list. 890 return (valueDescriptors.size() > 0 || occurrenceDescriptors.size() > 0); 891 } 892 893 /** 894 * Checks all the {@link ValueDisplayDescriptor} for similarity. 895 * If all the event values are from the same tag, the method will return EVENT_CHECK_SAME_TAG. 896 * If all the event/value are the same, the method will return EVENT_CHECK_SAME_VALUE 897 * 898 * @return flag as described above 899 */ 900 private int checkDescriptors() { 901 if (mValueDescriptors.size() < 2) { 902 return EVENT_CHECK_SAME_VALUE; 903 } 904 905 int tag = -1; 906 int index = -1; 907 for (ValueDisplayDescriptor display : mValueDescriptors) { 908 if (tag == -1) { 909 tag = display.eventTag; 910 index = display.valueIndex; 911 } else { 912 if (tag != display.eventTag) { 913 return EVENT_CHECK_FAILED; 914 } else { 915 if (index != -1) { 916 if (index != display.valueIndex) { 917 index = -1; 918 } 919 } 920 } 921 } 922 } 923 924 if (index == -1) { 925 return EVENT_CHECK_SAME_TAG; 926 } 927 928 return EVENT_CHECK_SAME_VALUE; 929 } 930 931 /** 932 * Resets the time limit on the chart to be infinite. 933 */ 934 void resetChartTimeLimit() { 935 mMaximumChartItemAge = -1; 936 } 937 938 /** 939 * Sets the time limit on the charts. 940 * 941 * @param timeLimit the time limit in seconds. 942 */ 943 void setChartTimeLimit(long timeLimit) { 944 mMaximumChartItemAge = timeLimit; 945 } 946 947 long getChartTimeLimit() { 948 return mMaximumChartItemAge; 949 } 950 951 /** 952 * m 953 * Resets the histogram width 954 */ 955 void resetHistWidth() { 956 mHistWidth = 1; 957 } 958 959 /** 960 * Sets the histogram width 961 * 962 * @param histWidth the width in hours 963 */ 964 void setHistWidth(long histWidth) { 965 mHistWidth = histWidth; 966 } 967 968 long getHistWidth() { 969 return mHistWidth; 970 } 971 } 972