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