1/* 2 * Copyright (C) 2024 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 {TracePositionUpdate, WinscopeEvent} from 'messaging/winscope_event'; 20import { 21 EmitEvent, 22 WinscopeEventEmitter, 23} from 'messaging/winscope_event_emitter'; 24import {Trace, TraceEntry} from 'trace/trace'; 25import {Traces} from 'trace/traces'; 26import {TraceEntryFinder} from 'trace/trace_entry_finder'; 27import {TraceType} from 'trace/trace_type'; 28import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node'; 29import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 30import {PropertiesPresenter} from 'viewers/common/properties_presenter'; 31import {RectsPresenter} from 'viewers/common/rects_presenter'; 32import {UiHierarchyTreeNode} from 'viewers/common/ui_hierarchy_tree_node'; 33import {UserOptions} from 'viewers/common/user_options'; 34import {HierarchyPresenter} from './hierarchy_presenter'; 35import {RectShowState} from './rect_show_state'; 36import {UiDataHierarchy} from './ui_data_hierarchy'; 37import {ViewerEvents} from './viewer_events'; 38 39export type NotifyHierarchyViewCallbackType = (uiData: UiDataHierarchy) => void; 40 41export abstract class AbstractHierarchyViewerPresenter 42 implements WinscopeEventEmitter 43{ 44 protected emitWinscopeEvent: EmitEvent = FunctionUtils.DO_NOTHING_ASYNC; 45 protected overridePropertiesTree: PropertyTreeNode | undefined; 46 protected overridePropertiesTreeName: string | undefined; 47 protected rectsPresenter?: RectsPresenter; 48 protected abstract hierarchyPresenter: HierarchyPresenter; 49 protected abstract propertiesPresenter: PropertiesPresenter; 50 protected abstract readonly multiTraceType?: TraceType; 51 private highlightedItem = ''; 52 53 constructor( 54 private readonly trace: Trace<HierarchyTreeNode> | undefined, 55 protected readonly traces: Traces, 56 protected readonly storage: Readonly<Storage>, 57 private readonly notifyViewCallback: NotifyHierarchyViewCallbackType, 58 protected uiData: UiDataHierarchy, 59 ) { 60 this.copyUiDataAndNotifyView(); 61 } 62 63 setEmitEvent(callback: EmitEvent) { 64 this.emitWinscopeEvent = callback; 65 } 66 67 addEventListeners(htmlElement: HTMLElement) { 68 htmlElement.addEventListener(ViewerEvents.HierarchyPinnedChange, (event) => 69 this.onPinnedItemChange((event as CustomEvent).detail.pinnedItem), 70 ); 71 htmlElement.addEventListener( 72 ViewerEvents.HighlightedIdChange, 73 async (event) => 74 await this.onHighlightedIdChange((event as CustomEvent).detail.id), 75 ); 76 htmlElement.addEventListener( 77 ViewerEvents.HighlightedPropertyChange, 78 (event) => 79 this.onHighlightedPropertyChange((event as CustomEvent).detail.id), 80 ); 81 htmlElement.addEventListener( 82 ViewerEvents.HierarchyUserOptionsChange, 83 async (event) => 84 await this.onHierarchyUserOptionsChange( 85 (event as CustomEvent).detail.userOptions, 86 ), 87 ); 88 htmlElement.addEventListener( 89 ViewerEvents.HierarchyFilterChange, 90 async (event) => 91 await this.onHierarchyFilterChange( 92 (event as CustomEvent).detail.filterString, 93 ), 94 ); 95 htmlElement.addEventListener( 96 ViewerEvents.PropertiesUserOptionsChange, 97 async (event) => 98 await this.onPropertiesUserOptionsChange( 99 (event as CustomEvent).detail.userOptions, 100 ), 101 ); 102 htmlElement.addEventListener( 103 ViewerEvents.PropertiesFilterChange, 104 async (event) => 105 await this.onPropertiesFilterChange( 106 (event as CustomEvent).detail.filterString, 107 ), 108 ); 109 htmlElement.addEventListener( 110 ViewerEvents.HighlightedNodeChange, 111 async (event) => 112 await this.onHighlightedNodeChange((event as CustomEvent).detail.node), 113 ); 114 htmlElement.addEventListener( 115 ViewerEvents.RectShowStateChange, 116 async (event) => { 117 await this.onRectShowStateChange( 118 (event as CustomEvent).detail.rectId, 119 (event as CustomEvent).detail.state, 120 ); 121 }, 122 ); 123 htmlElement.addEventListener( 124 ViewerEvents.RectsUserOptionsChange, 125 (event) => { 126 this.onRectsUserOptionsChange( 127 (event as CustomEvent).detail.userOptions, 128 ); 129 }, 130 ); 131 } 132 133 onPinnedItemChange(pinnedItem: UiHierarchyTreeNode) { 134 this.hierarchyPresenter.applyPinnedItemChange(pinnedItem); 135 this.uiData.pinnedItems = this.hierarchyPresenter.getPinnedItems(); 136 this.copyUiDataAndNotifyView(); 137 } 138 139 onHighlightedPropertyChange(id: string) { 140 this.propertiesPresenter.applyHighlightedPropertyChange(id); 141 this.uiData.highlightedProperty = 142 this.propertiesPresenter.getHighlightedProperty(); 143 this.copyUiDataAndNotifyView(); 144 } 145 146 onRectsUserOptionsChange(userOptions: UserOptions) { 147 if (!this.rectsPresenter) { 148 return; 149 } 150 this.rectsPresenter.applyRectsUserOptionsChange(userOptions); 151 152 this.uiData.rectsUserOptions = this.rectsPresenter.getUserOptions(); 153 this.uiData.rectsToDraw = this.rectsPresenter.getRectsToDraw(); 154 this.uiData.rectIdToShowState = this.rectsPresenter.getRectIdToShowState(); 155 156 this.copyUiDataAndNotifyView(); 157 } 158 159 async onHierarchyUserOptionsChange(userOptions: UserOptions) { 160 await this.hierarchyPresenter.applyHierarchyUserOptionsChange(userOptions); 161 this.uiData.hierarchyUserOptions = this.hierarchyPresenter.getUserOptions(); 162 this.uiData.hierarchyTrees = this.hierarchyPresenter.getAllFormattedTrees(); 163 this.copyUiDataAndNotifyView(); 164 } 165 166 async onHierarchyFilterChange(filterString: string) { 167 await this.hierarchyPresenter.applyHierarchyFilterChange(filterString); 168 this.uiData.hierarchyTrees = this.hierarchyPresenter.getAllFormattedTrees(); 169 this.copyUiDataAndNotifyView(); 170 } 171 172 async onPropertiesUserOptionsChange(userOptions: UserOptions) { 173 if (!this.propertiesPresenter) { 174 return; 175 } 176 this.propertiesPresenter.applyPropertiesUserOptionsChange(userOptions); 177 await this.updatePropertiesTree(); 178 this.uiData.propertiesUserOptions = 179 this.propertiesPresenter.getUserOptions(); 180 this.uiData.propertiesTree = this.propertiesPresenter.getFormattedTree(); 181 this.copyUiDataAndNotifyView(); 182 } 183 184 async onPropertiesFilterChange(filterString: string) { 185 if (!this.propertiesPresenter) { 186 return; 187 } 188 this.propertiesPresenter.applyPropertiesFilterChange(filterString); 189 await this.updatePropertiesTree(); 190 this.uiData.propertiesTree = this.propertiesPresenter.getFormattedTree(); 191 this.copyUiDataAndNotifyView(); 192 } 193 194 async onRectShowStateChange(id: string, newShowState: RectShowState) { 195 if (!this.rectsPresenter) { 196 return; 197 } 198 this.rectsPresenter.applyRectShowStateChange(id, newShowState); 199 200 this.uiData.rectsToDraw = this.rectsPresenter.getRectsToDraw(); 201 this.uiData.rectIdToShowState = this.rectsPresenter.getRectIdToShowState(); 202 this.copyUiDataAndNotifyView(); 203 } 204 205 protected async applyTracePositionUpdate(event: TracePositionUpdate) { 206 let entries: Array<TraceEntry<HierarchyTreeNode>> = []; 207 if (this.multiTraceType !== undefined) { 208 entries = this.traces 209 .getTraces(this.multiTraceType) 210 .map((trace) => { 211 return TraceEntryFinder.findCorrespondingEntry( 212 trace, 213 event.position, 214 ) as TraceEntry<HierarchyTreeNode> | undefined; 215 }) 216 .filter((entry) => entry !== undefined) as Array< 217 TraceEntry<HierarchyTreeNode> 218 >; 219 } else { 220 const entry = TraceEntryFinder.findCorrespondingEntry( 221 assertDefined(this.trace), 222 event.position, 223 ); 224 if (entry) entries.push(entry); 225 } 226 227 await this.hierarchyPresenter.applyTracePositionUpdate( 228 entries, 229 this.highlightedItem, 230 ); 231 232 const propertiesOpts = this.propertiesPresenter.getUserOptions(); 233 const hasPreviousEntry = entries.some((e) => e.getIndex() > 0); 234 if (propertiesOpts['showDiff']?.isUnavailable !== undefined) { 235 propertiesOpts['showDiff'].isUnavailable = !hasPreviousEntry; 236 } 237 238 const currentHierarchyTrees = 239 this.hierarchyPresenter.getAllCurrentHierarchyTrees(); 240 if (currentHierarchyTrees) { 241 this.rectsPresenter?.applyHierarchyTreesChange(currentHierarchyTrees); 242 await this.updatePropertiesTree(); 243 } 244 } 245 246 protected async applyHighlightedNodeChange(node: UiHierarchyTreeNode) { 247 this.updateHighlightedItem(node.id); 248 this.hierarchyPresenter.applyHighlightedNodeChange(node); 249 await this.updatePropertiesTree(); 250 } 251 252 protected async applyHighlightedIdChange(newId: string) { 253 this.updateHighlightedItem(newId); 254 this.hierarchyPresenter.applyHighlightedIdChange(newId); 255 await this.updatePropertiesTree(); 256 } 257 258 protected async updatePropertiesTree() { 259 if (this.overridePropertiesTree) { 260 this.propertiesPresenter.setPropertiesTree(this.overridePropertiesTree); 261 await this.propertiesPresenter.formatPropertiesTree( 262 undefined, 263 this.overridePropertiesTreeName, 264 false, 265 ); 266 return; 267 } 268 const selected = this.hierarchyPresenter.getSelectedTree(); 269 if (selected) { 270 const [trace, selectedTree] = selected; 271 const propertiesTree = await selectedTree.getAllProperties(); 272 if ( 273 this.propertiesPresenter.getUserOptions()['showDiff']?.enabled && 274 !this.hierarchyPresenter.getPreviousHierarchyTreeForTrace(trace) 275 ) { 276 await this.hierarchyPresenter.updatePreviousHierarchyTrees(); 277 } 278 const previousTree = 279 this.hierarchyPresenter.getPreviousHierarchyTreeForTrace(trace); 280 this.propertiesPresenter.setPropertiesTree(propertiesTree); 281 await this.propertiesPresenter.formatPropertiesTree( 282 previousTree, 283 this.getOverrideDisplayName(selected), 284 this.keepCalculated(selectedTree), 285 ); 286 } else { 287 this.propertiesPresenter.clear(); 288 } 289 } 290 291 protected updateHighlightedItem(id: string) { 292 if (this.highlightedItem === id) { 293 this.highlightedItem = ''; 294 } else { 295 this.highlightedItem = id; 296 } 297 } 298 299 protected refreshHierarchyViewerUiData(uiData: UiDataHierarchy) { 300 this.uiData = uiData; 301 this.uiData.highlightedItem = this.highlightedItem; 302 this.uiData.pinnedItems = this.hierarchyPresenter.getPinnedItems(); 303 this.uiData.hierarchyUserOptions = this.hierarchyPresenter.getUserOptions(); 304 this.uiData.hierarchyTrees = this.hierarchyPresenter.getAllFormattedTrees(); 305 306 this.uiData.propertiesUserOptions = 307 this.propertiesPresenter.getUserOptions(); 308 this.uiData.propertiesTree = this.propertiesPresenter.getFormattedTree(); 309 this.uiData.highlightedProperty = 310 this.propertiesPresenter.getHighlightedProperty(); 311 312 if (this.rectsPresenter) { 313 this.uiData.rectsToDraw = this.rectsPresenter?.getRectsToDraw(); 314 this.uiData.rectIdToShowState = 315 this.rectsPresenter.getRectIdToShowState(); 316 this.uiData.displays = this.rectsPresenter.getDisplays(); 317 this.uiData.rectsUserOptions = this.rectsPresenter.getUserOptions(); 318 } 319 320 this.copyUiDataAndNotifyView(); 321 } 322 323 protected getHighlightedItem(): string | undefined { 324 return this.highlightedItem; 325 } 326 327 private copyUiDataAndNotifyView() { 328 // Create a shallow copy of the data, otherwise the Angular OnPush change detection strategy 329 // won't detect the new input 330 const copy = Object.assign({}, this.uiData); 331 this.notifyViewCallback(copy); 332 } 333 334 abstract onAppEvent(event: WinscopeEvent): Promise<void>; 335 abstract onHighlightedNodeChange(node: UiHierarchyTreeNode): Promise<void>; 336 abstract onHighlightedIdChange(id: string): Promise<void>; 337 protected abstract keepCalculated(tree: HierarchyTreeNode): boolean; 338 protected abstract getOverrideDisplayName( 339 selected: [Trace<HierarchyTreeNode>, HierarchyTreeNode], 340 ): string | undefined; 341} 342