1/* 2 * Copyright (C) 2023 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 {assertDefined} from 'common/assert_utils'; 18import {FunctionUtils} from 'common/function_utils'; 19import {Timestamp} from 'common/time'; 20import { 21 TracePositionUpdate, 22 WinscopeEvent, 23 WinscopeEventType, 24} from 'messaging/winscope_event'; 25import { 26 EmitEvent, 27 WinscopeEventEmitter, 28} from 'messaging/winscope_event_emitter'; 29import {CustomQueryType} from 'trace/custom_query'; 30import {AbsoluteEntryIndex, Trace} from 'trace/trace'; 31import {Traces} from 'trace/traces'; 32import {TraceEntryFinder} from 'trace/trace_entry_finder'; 33import {TraceType} from 'trace/trace_type'; 34import {Transition} from 'trace/transition'; 35import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node'; 36import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 37import {Filter} from 'viewers/common/operations/filter'; 38import {UiPropertyTreeNode} from 'viewers/common/ui_property_tree_node'; 39import {UiTreeFormatter} from 'viewers/common/ui_tree_formatter'; 40import {UiTreeUtils} from 'viewers/common/ui_tree_utils'; 41import {UpdateTransitionChangesNames} from './operations/update_transition_changes_names'; 42import {UiData} from './ui_data'; 43 44export class Presenter implements WinscopeEventEmitter { 45 private isInitialized = false; 46 private transitionTrace: Trace<PropertyTreeNode>; 47 private surfaceFlingerTrace: Trace<HierarchyTreeNode> | undefined; 48 private windowManagerTrace: Trace<HierarchyTreeNode> | undefined; 49 private layerIdToName = new Map<number, string>(); 50 private windowTokenToTitle = new Map<string, string>(); 51 private uiData = UiData.EMPTY; 52 private readonly notifyUiDataCallback: (data: UiData) => void; 53 private emitAppEvent: EmitEvent = FunctionUtils.DO_NOTHING_ASYNC; 54 55 constructor( 56 trace: Trace<PropertyTreeNode>, 57 traces: Traces, 58 notifyUiDataCallback: (data: UiData) => void, 59 ) { 60 this.transitionTrace = trace; 61 this.surfaceFlingerTrace = traces.getTrace(TraceType.SURFACE_FLINGER); 62 this.windowManagerTrace = traces.getTrace(TraceType.WINDOW_MANAGER); 63 this.notifyUiDataCallback = notifyUiDataCallback; 64 } 65 66 setEmitEvent(callback: EmitEvent) { 67 this.emitAppEvent = callback; 68 } 69 70 async onAppEvent(event: WinscopeEvent) { 71 await event.visit( 72 WinscopeEventType.TRACE_POSITION_UPDATE, 73 async (event) => { 74 await this.initializeIfNeeded(); 75 76 if (this.uiData === UiData.EMPTY) { 77 this.uiData = await this.computeUiData(); 78 } 79 80 const entry = TraceEntryFinder.findCorrespondingEntry( 81 this.transitionTrace, 82 event.position, 83 ); 84 85 const transition = await entry?.getValue(); 86 if (transition !== undefined) { 87 this.onTransitionSelected(transition); 88 } 89 90 this.notifyUiDataCallback(this.uiData); 91 }, 92 ); 93 } 94 95 onTransitionSelected(transition: PropertyTreeNode): void { 96 this.uiData.selectedTransition = this.makeUiPropertiesTree(transition); 97 this.notifyUiDataCallback(this.uiData); 98 } 99 100 async onRawTimestampClicked(timestamp: Timestamp) { 101 await this.emitAppEvent(TracePositionUpdate.fromTimestamp(timestamp, true)); 102 } 103 104 async onLogTimestampClicked(traceIndex: AbsoluteEntryIndex) { 105 await this.emitAppEvent( 106 TracePositionUpdate.fromTraceEntry( 107 this.transitionTrace.getEntry(traceIndex), 108 true, 109 ), 110 ); 111 } 112 113 private async initializeIfNeeded() { 114 if (this.isInitialized) { 115 return; 116 } 117 118 if (this.surfaceFlingerTrace) { 119 const layersIdAndName = await this.surfaceFlingerTrace.customQuery( 120 CustomQueryType.SF_LAYERS_ID_AND_NAME, 121 ); 122 layersIdAndName.forEach((value) => { 123 this.layerIdToName.set(value.id, value.name); 124 }); 125 } 126 127 if (this.windowManagerTrace) { 128 const windowsTokenAndTitle = await this.windowManagerTrace.customQuery( 129 CustomQueryType.WM_WINDOWS_TOKEN_AND_TITLE, 130 ); 131 windowsTokenAndTitle.forEach((value) => { 132 this.windowTokenToTitle.set(value.token, value.title); 133 }); 134 } 135 136 this.isInitialized = true; 137 } 138 139 private async computeUiData(): Promise<UiData> { 140 const entryPromises = this.transitionTrace.mapEntry((entry) => { 141 return entry.getValue(); 142 }); 143 const entries = await Promise.all(entryPromises); 144 // TODO(b/339191691): Ideally we should refactor the parsers to 145 // keep a map of time -> rowId, instead of relying on table order 146 const transitions = this.makeTransitions(entries); 147 this.sortTransitions(transitions); 148 const selectedTransition = this.uiData?.selectedTransition ?? undefined; 149 150 return new UiData(transitions, selectedTransition); 151 } 152 153 private sortTransitions(transitions: Transition[]) { 154 transitions.sort((a: Transition, b: Transition) => { 155 return a.id <= b.id ? -1 : 1; 156 }); 157 } 158 159 private makeTransitions(entries: PropertyTreeNode[]): Transition[] { 160 return entries.map((transitionNode, index) => { 161 const wmDataNode = assertDefined(transitionNode.getChildByName('wmData')); 162 const shellDataNode = assertDefined( 163 transitionNode.getChildByName('shellData'), 164 ); 165 166 const transition: Transition = { 167 id: assertDefined(transitionNode.getChildByName('id')).getValue(), 168 type: wmDataNode.getChildByName('type')?.formattedValue() ?? 'NONE', 169 sendTime: wmDataNode.getChildByName('sendTimeNs'), 170 dispatchTime: shellDataNode.getChildByName('dispatchTimeNs'), 171 duration: transitionNode.getChildByName('duration')?.formattedValue(), 172 merged: assertDefined( 173 transitionNode.getChildByName('merged'), 174 ).getValue(), 175 aborted: assertDefined( 176 transitionNode.getChildByName('aborted'), 177 ).getValue(), 178 played: assertDefined( 179 transitionNode.getChildByName('played'), 180 ).getValue(), 181 propertiesTree: transitionNode, 182 traceIndex: index, 183 }; 184 return transition; 185 }); 186 } 187 188 private makeUiPropertiesTree( 189 transitionNode: PropertyTreeNode, 190 ): UiPropertyTreeNode { 191 const tree = UiPropertyTreeNode.from(transitionNode); 192 193 return new UiTreeFormatter<UiPropertyTreeNode>() 194 .setUiTree(tree) 195 .addOperation(new Filter([UiTreeUtils.isNotCalculated], false)) 196 .addOperation( 197 new UpdateTransitionChangesNames( 198 this.layerIdToName, 199 this.windowTokenToTitle, 200 ), 201 ) 202 .format(); 203 } 204} 205