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 {TimeUtils} from 'common/time_utils'; 19import {LayerTraceEntry, Transition, WindowManagerState} from 'trace/flickerlib/common'; 20import {Trace} from 'trace/trace'; 21import {Traces} from 'trace/traces'; 22import {TraceEntryFinder} from 'trace/trace_entry_finder'; 23import {TracePosition} from 'trace/trace_position'; 24import {TraceType} from 'trace/trace_type'; 25import {PropertiesTreeNode} from 'viewers/common/ui_tree_utils'; 26import {UiData} from './ui_data'; 27 28export class Presenter { 29 private transitionTrace: Trace<object>; 30 private surfaceFlingerTrace: Trace<object> | undefined; 31 private windowManagerTrace: Trace<object> | undefined; 32 private uiData = UiData.EMPTY; 33 private readonly notifyUiDataCallback: (data: UiData) => void; 34 35 constructor(traces: Traces, notifyUiDataCallback: (data: UiData) => void) { 36 this.transitionTrace = assertDefined(traces.getTrace(TraceType.TRANSITION)); 37 this.surfaceFlingerTrace = traces.getTrace(TraceType.SURFACE_FLINGER); 38 this.windowManagerTrace = traces.getTrace(TraceType.WINDOW_MANAGER); 39 this.notifyUiDataCallback = notifyUiDataCallback; 40 } 41 42 async onTracePositionUpdate(position: TracePosition) { 43 if (this.uiData === UiData.EMPTY) { 44 this.uiData = await this.computeUiData(); 45 } 46 47 const entry = TraceEntryFinder.findCorrespondingEntry(this.transitionTrace, position); 48 49 this.uiData.selectedTransition = await entry?.getValue(); 50 51 this.notifyUiDataCallback(this.uiData); 52 } 53 54 onTransitionSelected(transition: Transition): void { 55 this.uiData.selectedTransition = transition; 56 this.uiData.selectedTransitionPropertiesTree = 57 this.makeSelectedTransitionPropertiesTree(transition); 58 this.notifyUiDataCallback(this.uiData); 59 } 60 61 private async computeUiData(): Promise<UiData> { 62 const entryPromises = this.transitionTrace.mapEntry((entry, originalIndex) => { 63 return entry.getValue(); 64 }); 65 const transitions = await Promise.all(entryPromises); 66 67 const selectedTransition = this.uiData?.selectedTransition ?? undefined; 68 const selectedTransitionPropertiesTree = 69 this.uiData?.selectedTransitionPropertiesTree ?? undefined; 70 71 const timestampType = this.transitionTrace.getTimestampType(); 72 if (timestampType === undefined) { 73 throw new Error('Missing timestamp type in trace!'); 74 } 75 return new UiData( 76 transitions, 77 selectedTransition, 78 timestampType, 79 selectedTransitionPropertiesTree 80 ); 81 } 82 83 private makeSelectedTransitionPropertiesTree(transition: Transition): PropertiesTreeNode { 84 const changes: PropertiesTreeNode[] = []; 85 86 for (const change of transition.changes) { 87 let layerName: string | undefined = undefined; 88 let windowName: string | undefined = undefined; 89 90 if (this.surfaceFlingerTrace) { 91 this.surfaceFlingerTrace.forEachEntry((entry, originalIndex) => { 92 if (layerName !== undefined) { 93 return; 94 } 95 const layerTraceEntry = entry.getValue() as LayerTraceEntry; 96 for (const layer of layerTraceEntry.flattenedLayers) { 97 if (layer.id === change.layerId) { 98 layerName = layer.name; 99 } 100 } 101 }); 102 } 103 104 if (this.windowManagerTrace) { 105 this.windowManagerTrace.forEachEntry((entry, originalIndex) => { 106 if (windowName !== undefined) { 107 return; 108 } 109 const wmState = entry.getValue() as WindowManagerState; 110 for (const window of wmState.windowContainers) { 111 if (window.token.toLowerCase() === change.windowId.toString(16).toLowerCase()) { 112 windowName = window.title; 113 } 114 } 115 }); 116 } 117 118 const layerIdValue = layerName ? `${change.layerId} (${layerName})` : change.layerId; 119 const windowIdValue = windowName 120 ? `0x${change.windowId.toString(16)} (${windowName})` 121 : `0x${change.windowId.toString(16)}`; 122 123 changes.push({ 124 propertyKey: 'change', 125 children: [ 126 {propertyKey: 'transitMode', propertyValue: change.transitMode}, 127 {propertyKey: 'layerId', propertyValue: layerIdValue}, 128 {propertyKey: 'windowId', propertyValue: windowIdValue}, 129 ], 130 }); 131 } 132 133 const properties: PropertiesTreeNode[] = [ 134 {propertyKey: 'id', propertyValue: transition.id}, 135 {propertyKey: 'type', propertyValue: transition.type}, 136 {propertyKey: 'aborted', propertyValue: `${transition.aborted}`}, 137 ]; 138 139 if (transition.handler) { 140 properties.push({propertyKey: 'handler', propertyValue: transition.handler}); 141 } 142 143 const timestampType = this.transitionTrace.getTimestampType(); 144 145 if (!transition.createTime.isMin) { 146 properties.push({ 147 propertyKey: 'createTime', 148 propertyValue: TimeUtils.formattedKotlinTimestamp(transition.createTime, timestampType), 149 }); 150 } 151 152 if (!transition.sendTime.isMin) { 153 properties.push({ 154 propertyKey: 'sendTime', 155 propertyValue: TimeUtils.formattedKotlinTimestamp(transition.sendTime, timestampType), 156 }); 157 } 158 159 if (!transition.dispatchTime.isMin) { 160 properties.push({ 161 propertyKey: 'dispatchTime', 162 propertyValue: TimeUtils.formattedKotlinTimestamp(transition.dispatchTime, timestampType), 163 }); 164 } 165 166 if (!transition.finishTime.isMax) { 167 properties.push({ 168 propertyKey: 'finishTime', 169 propertyValue: TimeUtils.formattedKotlinTimestamp(transition.finishTime, timestampType), 170 }); 171 } 172 173 if (transition.mergeRequestTime) { 174 properties.push({ 175 propertyKey: 'mergeRequestTime', 176 propertyValue: TimeUtils.formattedKotlinTimestamp( 177 transition.mergeRequestTime, 178 timestampType 179 ), 180 }); 181 } 182 183 if (transition.shellAbortTime) { 184 properties.push({ 185 propertyKey: 'shellAbortTime', 186 propertyValue: TimeUtils.formattedKotlinTimestamp(transition.shellAbortTime, timestampType), 187 }); 188 } 189 190 if (transition.mergeTime) { 191 properties.push({ 192 propertyKey: 'mergeTime', 193 propertyValue: TimeUtils.formattedKotlinTimestamp(transition.mergeTime, timestampType), 194 }); 195 } 196 197 if (transition.mergedInto) { 198 properties.push({propertyKey: 'mergedInto', propertyValue: transition.mergedInto}); 199 } 200 201 if (transition.startTransactionId !== -1) { 202 properties.push({ 203 propertyKey: 'startTransactionId', 204 propertyValue: transition.startTransactionId, 205 }); 206 } 207 if (transition.finishTransactionId !== -1) { 208 properties.push({ 209 propertyKey: 'finishTransactionId', 210 propertyValue: transition.finishTransactionId, 211 }); 212 } 213 if (changes.length > 0) { 214 properties.push({propertyKey: 'changes', children: changes}); 215 } 216 217 return { 218 children: properties, 219 propertyKey: 'Selected Transition', 220 }; 221 } 222} 223