1/* 2 * Copyright (C) 2022 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 17import {ArrayUtils} from 'common/array_utils'; 18import {assertDefined} from 'common/assert_utils'; 19import {FunctionUtils} from 'common/function_utils'; 20import {PersistentStoreProxy} from 'common/persistent_store_proxy'; 21import { 22 TracePositionUpdate, 23 WinscopeEvent, 24 WinscopeEventType, 25} from 'messaging/winscope_event'; 26import { 27 EmitEvent, 28 WinscopeEventEmitter, 29} from 'messaging/winscope_event_emitter'; 30import {AbsoluteEntryIndex} from 'trace/index_types'; 31import {Trace, TraceEntry} from 'trace/trace'; 32import {TraceEntryFinder} from 'trace/trace_entry_finder'; 33import {TIMESTAMP_NODE_FORMATTER} from 'trace/tree_node/formatters'; 34import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 35import {DEFAULT_PROPERTY_TREE_NODE_FACTORY} from 'trace/tree_node/property_tree_node_factory'; 36import {Filter} from 'viewers/common/operations/filter'; 37import {UiPropertyTreeNode} from 'viewers/common/ui_property_tree_node'; 38import {UiTreeFormatter} from 'viewers/common/ui_tree_formatter'; 39import {UiTreeUtils} from 'viewers/common/ui_tree_utils'; 40import {UserOptions} from 'viewers/common/user_options'; 41import {SetRootDisplayNames} from './operations/set_root_display_name'; 42import {UiData, UiDataEntry, UiDataEntryType} from './ui_data'; 43 44type NotifyViewCallbackType = (uiData: UiData) => void; 45 46export class Presenter implements WinscopeEventEmitter { 47 private emitAppEvent: EmitEvent = FunctionUtils.DO_NOTHING_ASYNC; 48 private readonly trace: Trace<PropertyTreeNode>; 49 private entry?: TraceEntry<PropertyTreeNode>; 50 private originalIndicesOfUiDataEntries: number[]; 51 private uiData = UiData.EMPTY; 52 53 private isInitialized = false; 54 private allUiDataEntries: UiDataEntry[] = []; 55 private allVSyncIds: string[] = []; 56 private allPids: string[] = []; 57 private allUids: string[] = []; 58 private allTypes: string[] = []; 59 private allLayerAndDisplayIds: string[] = []; 60 private allTransactionIds: string[] = []; 61 private allFlags: string[] = []; 62 63 private vsyncIdFilter: string[] = []; 64 private pidFilter: string[] = []; 65 private uidFilter: string[] = []; 66 private typeFilter: string[] = []; 67 private layerIdFilter: string[] = []; 68 private whatFilter: string[] = []; 69 private transactionIdFilter: string[] = []; 70 71 private currentPropertiesTree: PropertyTreeNode | undefined; 72 73 private propertiesUserOptions: UserOptions = 74 PersistentStoreProxy.new<UserOptions>( 75 'TransactionsPropertyOptions', 76 { 77 showDefaults: { 78 name: 'Show defaults', 79 enabled: false, 80 tooltip: ` 81 If checked, shows the value of all properties. 82 Otherwise, hides all properties whose value is 83 the default for its data type. 84 `, 85 }, 86 }, 87 this.storage, 88 ); 89 90 private readonly notifyUiDataCallback: NotifyViewCallbackType; 91 private static readonly VALUE_NA = 'N/A'; 92 93 constructor( 94 trace: Trace<PropertyTreeNode>, 95 private readonly storage: Storage, 96 notifyViewCallback: NotifyViewCallbackType, 97 ) { 98 this.trace = trace; 99 this.notifyUiDataCallback = notifyViewCallback; 100 this.originalIndicesOfUiDataEntries = []; 101 this.notifyUiDataCallback(this.uiData); 102 } 103 104 setEmitEvent(callback: EmitEvent) { 105 this.emitAppEvent = callback; 106 } 107 108 async onAppEvent(event: WinscopeEvent) { 109 await event.visit( 110 WinscopeEventType.TRACE_POSITION_UPDATE, 111 async (event) => { 112 await this.initializeIfNeeded(); 113 this.entry = TraceEntryFinder.findCorrespondingEntry( 114 this.trace, 115 event.position, 116 ); 117 this.uiData.currentEntryIndex = this.computeCurrentEntryIndex(); 118 this.uiData.selectedEntryIndex = undefined; 119 this.uiData.scrollToIndex = this.uiData.currentEntryIndex; 120 this.currentPropertiesTree = this.computePropertiesTree( 121 this.uiData.entries, 122 this.uiData.currentEntryIndex, 123 this.uiData.selectedEntryIndex, 124 ); 125 this.uiData.currentPropertiesTree = this.formatPropertiesTree( 126 this.currentPropertiesTree, 127 ); 128 129 this.notifyUiDataCallback(this.uiData); 130 }, 131 ); 132 } 133 134 onVSyncIdFilterChanged(vsyncIds: string[]) { 135 this.vsyncIdFilter = vsyncIds; 136 this.uiData = this.computeUiData(); 137 this.notifyUiDataCallback(this.uiData); 138 } 139 140 onPidFilterChanged(pids: string[]) { 141 this.pidFilter = pids; 142 this.uiData = this.computeUiData(); 143 this.notifyUiDataCallback(this.uiData); 144 } 145 146 onUidFilterChanged(uids: string[]) { 147 this.uidFilter = uids; 148 this.uiData = this.computeUiData(); 149 this.notifyUiDataCallback(this.uiData); 150 } 151 152 onTypeFilterChanged(types: string[]) { 153 this.typeFilter = types; 154 this.uiData = this.computeUiData(); 155 this.notifyUiDataCallback(this.uiData); 156 } 157 158 onLayerIdFilterChanged(ids: string[]) { 159 this.layerIdFilter = ids; 160 this.uiData = this.computeUiData(); 161 this.notifyUiDataCallback(this.uiData); 162 } 163 164 onWhatFilterChanged(flags: string[]) { 165 this.whatFilter = flags; 166 this.uiData = this.computeUiData(); 167 this.notifyUiDataCallback(this.uiData); 168 } 169 170 onTransactionIdFilterChanged(ids: string[]) { 171 this.transactionIdFilter = ids; 172 this.uiData = this.computeUiData(); 173 this.notifyUiDataCallback(this.uiData); 174 } 175 176 onEntryClicked(index: number) { 177 if (this.uiData.selectedEntryIndex === index) { 178 return; 179 } 180 this.uiData.selectedEntryIndex = index; 181 this.uiData.scrollToIndex = undefined; // no scrolling 182 this.updatePropertiesTree(); 183 } 184 185 onEntryChangedByKeyPress(index: number) { 186 if (this.uiData.selectedEntryIndex === index) { 187 return; 188 } 189 this.uiData.selectedEntryIndex = index; 190 this.uiData.scrollToIndex = index; 191 this.updatePropertiesTree(); 192 } 193 194 onPropertiesUserOptionsChange(userOptions: UserOptions) { 195 this.propertiesUserOptions = userOptions; 196 this.uiData.propertiesUserOptions = this.propertiesUserOptions; 197 this.uiData.currentPropertiesTree = this.formatPropertiesTree( 198 this.currentPropertiesTree, 199 ); 200 this.notifyUiDataCallback(this.uiData); 201 } 202 203 async onLogTimestampClicked(traceIndex: AbsoluteEntryIndex) { 204 await this.emitAppEvent( 205 TracePositionUpdate.fromTraceEntry(this.trace.getEntry(traceIndex), true), 206 ); 207 } 208 209 private async initializeIfNeeded() { 210 if (this.isInitialized) { 211 return; 212 } 213 214 this.allUiDataEntries = await this.makeUiDataEntries(); 215 216 this.allVSyncIds = this.getUniqueUiDataEntryValues( 217 this.allUiDataEntries, 218 (entry: UiDataEntry) => entry.vsyncId.toString(), 219 ); 220 this.allPids = this.getUniqueUiDataEntryValues( 221 this.allUiDataEntries, 222 (entry: UiDataEntry) => entry.pid, 223 ); 224 this.allUids = this.getUniqueUiDataEntryValues( 225 this.allUiDataEntries, 226 (entry: UiDataEntry) => entry.uid, 227 ); 228 this.allTypes = this.getUniqueUiDataEntryValues( 229 this.allUiDataEntries, 230 (entry: UiDataEntry) => entry.type, 231 ); 232 this.allLayerAndDisplayIds = this.getUniqueUiDataEntryValues( 233 this.allUiDataEntries, 234 (entry: UiDataEntry) => entry.layerOrDisplayId, 235 ); 236 this.allTransactionIds = this.getUniqueUiDataEntryValues( 237 this.allUiDataEntries, 238 (entry: UiDataEntry) => entry.transactionId, 239 ); 240 this.allFlags = this.getUniqueUiDataEntryValues( 241 this.allUiDataEntries, 242 (entry: UiDataEntry) => entry.what.split('|').map((flag) => flag.trim()), 243 ); 244 245 this.uiData = this.computeUiData(); 246 247 this.isInitialized = true; 248 } 249 250 private computeUiData(): UiData { 251 const entries = this.allUiDataEntries; 252 253 let filteredEntries = entries; 254 255 if (this.vsyncIdFilter.length > 0) { 256 filteredEntries = filteredEntries.filter((entry) => 257 this.vsyncIdFilter.includes(entry.vsyncId.toString()), 258 ); 259 } 260 261 if (this.pidFilter.length > 0) { 262 filteredEntries = filteredEntries.filter((entry) => 263 this.pidFilter.includes(entry.pid), 264 ); 265 } 266 267 if (this.uidFilter.length > 0) { 268 filteredEntries = filteredEntries.filter((entry) => 269 this.uidFilter.includes(entry.uid), 270 ); 271 } 272 273 if (this.typeFilter.length > 0) { 274 filteredEntries = filteredEntries.filter((entry) => 275 this.typeFilter.includes(entry.type), 276 ); 277 } 278 279 if (this.layerIdFilter.length > 0) { 280 filteredEntries = filteredEntries.filter((entry) => 281 this.layerIdFilter.includes(entry.layerOrDisplayId), 282 ); 283 } 284 285 if (this.whatFilter.length > 0) { 286 filteredEntries = filteredEntries.filter( 287 (entry) => 288 this.whatFilter.find((flag) => entry.what.includes(flag)) !== 289 undefined, 290 ); 291 } 292 293 if (this.transactionIdFilter.length > 0) { 294 filteredEntries = filteredEntries.filter((entry) => 295 this.transactionIdFilter.includes(entry.transactionId.toString()), 296 ); 297 } 298 299 this.originalIndicesOfUiDataEntries = filteredEntries.map( 300 (entry) => entry.traceIndex, 301 ); 302 303 const currentEntryIndex = this.computeCurrentEntryIndex(); 304 const selectedEntryIndex = undefined; 305 this.currentPropertiesTree = this.computePropertiesTree( 306 filteredEntries, 307 currentEntryIndex, 308 selectedEntryIndex, 309 ); 310 311 const formattedPropertiesTree = this.formatPropertiesTree( 312 this.currentPropertiesTree, 313 ); 314 315 return new UiData( 316 this.allVSyncIds, 317 this.allPids, 318 this.allUids, 319 this.allTypes, 320 this.allLayerAndDisplayIds, 321 this.allTransactionIds, 322 this.allFlags, 323 filteredEntries, 324 currentEntryIndex, 325 selectedEntryIndex, 326 currentEntryIndex, 327 formattedPropertiesTree, 328 this.propertiesUserOptions, 329 ); 330 } 331 332 private computeCurrentEntryIndex(): undefined | number { 333 if (!this.entry) { 334 return undefined; 335 } 336 337 if (this.originalIndicesOfUiDataEntries.length === 0) { 338 return undefined; 339 } 340 341 return ( 342 ArrayUtils.binarySearchFirstGreaterOrEqual( 343 this.originalIndicesOfUiDataEntries, 344 this.entry.getIndex(), 345 ) ?? this.originalIndicesOfUiDataEntries.length - 1 346 ); 347 } 348 349 private updatePropertiesTree() { 350 this.currentPropertiesTree = this.computePropertiesTree( 351 this.uiData.entries, 352 this.uiData.currentEntryIndex, 353 this.uiData.selectedEntryIndex, 354 ); 355 356 this.uiData.currentPropertiesTree = this.formatPropertiesTree( 357 this.currentPropertiesTree, 358 ); 359 360 this.notifyUiDataCallback(this.uiData); 361 } 362 363 private computePropertiesTree( 364 entries: UiDataEntry[], 365 currentEntryIndex: undefined | number, 366 selectedEntryIndex: undefined | number, 367 ): PropertyTreeNode | undefined { 368 if (selectedEntryIndex !== undefined) { 369 return entries[selectedEntryIndex].propertiesTree; 370 } 371 if (currentEntryIndex !== undefined) { 372 return entries[currentEntryIndex].propertiesTree; 373 } 374 return undefined; 375 } 376 377 private formatPropertiesTree( 378 propertiesTree: PropertyTreeNode | undefined, 379 ): UiPropertyTreeNode | undefined { 380 if (!propertiesTree) return undefined; 381 382 const uiTree = UiPropertyTreeNode.from(propertiesTree); 383 const formatter = new UiTreeFormatter<UiPropertyTreeNode>().setUiTree( 384 uiTree, 385 ); 386 387 if (!this.propertiesUserOptions['showDefaults']?.enabled) { 388 formatter.addOperation( 389 new Filter( 390 [ 391 UiTreeUtils.isNotDefault, 392 UiTreeUtils.makePropertyMatchFilter('IDENTITY'), 393 ], 394 false, 395 ), 396 ); 397 } 398 399 return formatter.addOperation(new SetRootDisplayNames()).format(); 400 } 401 402 private async makeUiDataEntries(): Promise<UiDataEntry[]> { 403 const entries: UiDataEntry[] = []; 404 405 const entryProtos = await Promise.all( 406 this.trace.mapEntry(async (entry) => { 407 return await entry.getValue(); 408 }), 409 ); 410 411 for ( 412 let traceIndex = 0; 413 traceIndex < this.trace.lengthEntries; 414 ++traceIndex 415 ) { 416 const entry = this.trace.getEntry(traceIndex); 417 const entryNode = entryProtos[traceIndex]; 418 const vsyncId = Number( 419 assertDefined(entryNode.getChildByName('vsyncId')).getValue(), 420 ); 421 422 const entryTimestamp = 423 DEFAULT_PROPERTY_TREE_NODE_FACTORY.makeCalculatedProperty( 424 'TransactionsTraceEntry', 425 'timestamp', 426 entry.getTimestamp(), 427 ); 428 entryTimestamp.setFormatter(TIMESTAMP_NODE_FORMATTER); 429 430 for (const transactionState of assertDefined( 431 entryNode.getChildByName('transactions'), 432 ).getAllChildren()) { 433 const pid = assertDefined( 434 transactionState.getChildByName('pid'), 435 ).formattedValue(); 436 const uid = assertDefined( 437 transactionState.getChildByName('uid'), 438 ).formattedValue(); 439 const transactionId = assertDefined( 440 transactionState.getChildByName('transactionId'), 441 ).formattedValue(); 442 443 const layerChanges = assertDefined( 444 transactionState.getChildByName('layerChanges'), 445 ).getAllChildren(); 446 for (const layerState of layerChanges) { 447 entries.push( 448 new UiDataEntry( 449 traceIndex, 450 entryTimestamp, 451 vsyncId, 452 pid, 453 uid, 454 UiDataEntryType.LAYER_CHANGED, 455 assertDefined( 456 layerState.getChildByName('layerId'), 457 ).formattedValue(), 458 transactionId, 459 assertDefined(layerState.getChildByName('what')).formattedValue(), 460 layerState, 461 ), 462 ); 463 } 464 465 const displayChanges = assertDefined( 466 transactionState.getChildByName('displayChanges'), 467 ).getAllChildren(); 468 for (const displayState of displayChanges) { 469 entries.push( 470 new UiDataEntry( 471 traceIndex, 472 entryTimestamp, 473 vsyncId, 474 pid, 475 uid, 476 UiDataEntryType.DISPLAY_CHANGED, 477 assertDefined(displayState.getChildByName('id')).formattedValue(), 478 transactionId, 479 assertDefined( 480 displayState.getChildByName('what'), 481 ).formattedValue(), 482 displayState, 483 ), 484 ); 485 } 486 487 if (layerChanges.length === 0 && displayChanges.length === 0) { 488 entries.push( 489 new UiDataEntry( 490 traceIndex, 491 entryTimestamp, 492 vsyncId, 493 pid, 494 uid, 495 UiDataEntryType.NO_OP, 496 '', 497 transactionId, 498 '', 499 undefined, 500 ), 501 ); 502 } 503 } 504 505 for (const layerCreationArgs of assertDefined( 506 entryNode.getChildByName('addedLayers'), 507 ).getAllChildren()) { 508 entries.push( 509 new UiDataEntry( 510 traceIndex, 511 entryTimestamp, 512 vsyncId, 513 Presenter.VALUE_NA, 514 Presenter.VALUE_NA, 515 UiDataEntryType.LAYER_ADDED, 516 assertDefined( 517 layerCreationArgs.getChildByName('layerId'), 518 ).formattedValue(), 519 '', 520 '', 521 layerCreationArgs, 522 ), 523 ); 524 } 525 526 for (const destroyedLayerId of assertDefined( 527 entryNode.getChildByName('destroyedLayers'), 528 ).getAllChildren()) { 529 entries.push( 530 new UiDataEntry( 531 traceIndex, 532 entryTimestamp, 533 vsyncId, 534 Presenter.VALUE_NA, 535 Presenter.VALUE_NA, 536 UiDataEntryType.LAYER_DESTROYED, 537 destroyedLayerId.formattedValue(), 538 '', 539 '', 540 destroyedLayerId, 541 ), 542 ); 543 } 544 545 for (const displayState of assertDefined( 546 entryNode.getChildByName('addedDisplays'), 547 ).getAllChildren()) { 548 entries.push( 549 new UiDataEntry( 550 traceIndex, 551 entryTimestamp, 552 vsyncId, 553 Presenter.VALUE_NA, 554 Presenter.VALUE_NA, 555 UiDataEntryType.DISPLAY_ADDED, 556 assertDefined(displayState.getChildByName('id')).formattedValue(), 557 '', 558 assertDefined(displayState.getChildByName('what')).formattedValue(), 559 displayState, 560 ), 561 ); 562 } 563 564 for (const removedDisplayId of assertDefined( 565 entryNode.getChildByName('removedDisplays'), 566 ).getAllChildren()) { 567 entries.push( 568 new UiDataEntry( 569 traceIndex, 570 entryTimestamp, 571 vsyncId, 572 Presenter.VALUE_NA, 573 Presenter.VALUE_NA, 574 UiDataEntryType.DISPLAY_REMOVED, 575 removedDisplayId.formattedValue(), 576 '', 577 '', 578 removedDisplayId, 579 ), 580 ); 581 } 582 583 for (const destroyedLayerHandleId of assertDefined( 584 entryNode.getChildByName('destroyedLayerHandles'), 585 ).getAllChildren()) { 586 entries.push( 587 new UiDataEntry( 588 traceIndex, 589 entryTimestamp, 590 vsyncId, 591 Presenter.VALUE_NA, 592 Presenter.VALUE_NA, 593 UiDataEntryType.LAYER_HANDLE_DESTROYED, 594 destroyedLayerHandleId.formattedValue(), 595 '', 596 '', 597 destroyedLayerHandleId, 598 ), 599 ); 600 } 601 } 602 603 return entries; 604 } 605 606 private getUniqueUiDataEntryValues<T>( 607 entries: UiDataEntry[], 608 getValue: (entry: UiDataEntry) => T | T[], 609 ): T[] { 610 const uniqueValues = new Set<T>(); 611 entries.forEach((entry: UiDataEntry) => { 612 const value = getValue(entry); 613 if (Array.isArray(value)) { 614 value.forEach((val) => uniqueValues.add(val)); 615 } else { 616 uniqueValues.add(value); 617 } 618 }); 619 620 const result = [...uniqueValues]; 621 622 result.sort((a, b) => { 623 const aIsNumber = !isNaN(Number(a)); 624 const bIsNumber = !isNaN(Number(b)); 625 626 if (aIsNumber && bIsNumber) { 627 return Number(a) - Number(b); 628 } else if (aIsNumber) { 629 return 1; // place number after strings in the result 630 } else if (bIsNumber) { 631 return -1; // place number after strings in the result 632 } 633 634 // a and b are both strings 635 if (a < b) { 636 return -1; 637 } else if (a > b) { 638 return 1; 639 } else { 640 return 0; 641 } 642 }); 643 644 return result; 645 } 646} 647