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 {PersistentStoreProxy} from 'common/persistent_store_proxy'; 18import {FilterType, TreeUtils} from 'common/tree_utils'; 19import {LayerTraceEntry} from 'trace/flickerlib/layers/LayerTraceEntry'; 20import {WindowManagerState} from 'trace/flickerlib/windows/WindowManagerState'; 21import {Trace, TraceEntry} from 'trace/trace'; 22import {Traces} from 'trace/traces'; 23import {TraceEntryFinder} from 'trace/trace_entry_finder'; 24import {TracePosition} from 'trace/trace_position'; 25import {TraceTreeNode} from 'trace/trace_tree_node'; 26import {TraceType} from 'trace/trace_type'; 27import {ImeAdditionalProperties} from 'viewers/common/ime_additional_properties'; 28import {ImeUiData} from 'viewers/common/ime_ui_data'; 29import {ImeLayers, ImeUtils, ProcessedWindowManagerState} from 'viewers/common/ime_utils'; 30import {TableProperties} from 'viewers/common/table_properties'; 31import {TreeGenerator} from 'viewers/common/tree_generator'; 32import {TreeTransformer} from 'viewers/common/tree_transformer'; 33import {HierarchyTreeNode, PropertiesTreeNode} from 'viewers/common/ui_tree_utils'; 34import {UserOptions} from 'viewers/common/user_options'; 35 36type NotifyImeViewCallbackType = (uiData: ImeUiData) => void; 37 38export abstract class PresenterInputMethod { 39 private readonly imeTrace: Trace<object>; 40 private readonly wmTrace?: Trace<WindowManagerState>; 41 private readonly sfTrace?: Trace<LayerTraceEntry>; 42 private hierarchyFilter: FilterType = TreeUtils.makeNodeFilter(''); 43 private propertiesFilter: FilterType = TreeUtils.makeNodeFilter(''); 44 private pinnedItems: HierarchyTreeNode[] = []; 45 private pinnedIds: string[] = []; 46 private selectedHierarchyTree: HierarchyTreeNode | null = null; 47 48 readonly notifyViewCallback: NotifyImeViewCallbackType; 49 protected readonly dependencies: TraceType[]; 50 protected uiData: ImeUiData; 51 protected highlightedItems: string[] = []; 52 protected entry: TraceTreeNode | null = null; 53 protected additionalPropertyEntry: TraceTreeNode | null = null; 54 protected hierarchyUserOptions: UserOptions = PersistentStoreProxy.new<UserOptions>( 55 'ImeHierarchyOptions', 56 { 57 simplifyNames: { 58 name: 'Simplify names', 59 enabled: true, 60 }, 61 onlyVisible: { 62 name: 'Only visible', 63 enabled: false, 64 }, 65 flat: { 66 name: 'Flat', 67 enabled: false, 68 }, 69 }, 70 this.storage 71 ); 72 protected propertiesUserOptions: UserOptions = PersistentStoreProxy.new<UserOptions>( 73 'ImePropertiesOptions', 74 { 75 showDefaults: { 76 name: 'Show defaults', 77 enabled: false, 78 tooltip: ` 79 If checked, shows the value of all properties. 80 Otherwise, hides all properties whose value is 81 the default for its data type. 82 `, 83 }, 84 }, 85 this.storage 86 ); 87 88 constructor( 89 traces: Traces, 90 private storage: Storage, 91 dependencies: TraceType[], 92 notifyViewCallback: NotifyImeViewCallbackType 93 ) { 94 this.imeTrace = traces.getTrace(dependencies[0]) as Trace<TraceTreeNode>; 95 this.sfTrace = traces.getTrace(TraceType.SURFACE_FLINGER); 96 this.wmTrace = traces.getTrace(TraceType.WINDOW_MANAGER); 97 98 this.dependencies = dependencies; 99 this.notifyViewCallback = notifyViewCallback; 100 this.uiData = new ImeUiData(dependencies); 101 this.notifyViewCallback(this.uiData); 102 } 103 104 async onTracePositionUpdate(position: TracePosition) { 105 this.uiData = new ImeUiData(this.dependencies); 106 this.uiData.hierarchyUserOptions = this.hierarchyUserOptions; 107 this.uiData.propertiesUserOptions = this.propertiesUserOptions; 108 109 const [imeEntry, sfEntry, wmEntry] = this.findTraceEntries(position); 110 111 if (imeEntry) { 112 this.entry = (await imeEntry.getValue()) as TraceTreeNode; 113 this.uiData.highlightedItems = this.highlightedItems; 114 this.uiData.additionalProperties = this.getAdditionalProperties( 115 await wmEntry?.getValue(), 116 await sfEntry?.getValue() 117 ); 118 this.uiData.tree = this.generateTree(); 119 this.uiData.hierarchyTableProperties = this.updateHierarchyTableProperties(); 120 } 121 this.notifyViewCallback(this.uiData); 122 } 123 124 updatePinnedItems(pinnedItem: HierarchyTreeNode) { 125 const pinnedId = `${pinnedItem.id}`; 126 if (this.pinnedItems.map((item) => `${item.id}`).includes(pinnedId)) { 127 this.pinnedItems = this.pinnedItems.filter((pinned) => `${pinned.id}` !== pinnedId); 128 } else { 129 this.pinnedItems.push(pinnedItem); 130 } 131 this.updatePinnedIds(pinnedId); 132 this.uiData.pinnedItems = this.pinnedItems; 133 this.notifyViewCallback(this.uiData); 134 } 135 136 updateHighlightedItems(id: string) { 137 if (this.highlightedItems.includes(id)) { 138 this.highlightedItems = this.highlightedItems.filter((hl) => hl !== id); 139 } else { 140 this.highlightedItems = []; //if multi-select surfaces implemented, remove this line 141 this.highlightedItems.push(id); 142 } 143 this.uiData.highlightedItems = this.highlightedItems; 144 this.notifyViewCallback(this.uiData); 145 } 146 147 updateHierarchyTree(userOptions: UserOptions) { 148 this.hierarchyUserOptions = userOptions; 149 this.uiData.hierarchyUserOptions = this.hierarchyUserOptions; 150 this.uiData.tree = this.generateTree(); 151 this.notifyViewCallback(this.uiData); 152 } 153 154 filterHierarchyTree(filterString: string) { 155 this.hierarchyFilter = TreeUtils.makeNodeFilter(filterString); 156 this.uiData.tree = this.generateTree(); 157 this.notifyViewCallback(this.uiData); 158 } 159 160 updatePropertiesTree(userOptions: UserOptions) { 161 this.propertiesUserOptions = userOptions; 162 this.uiData.propertiesUserOptions = this.propertiesUserOptions; 163 this.updateSelectedTreeUiData(); 164 } 165 166 filterPropertiesTree(filterString: string) { 167 this.propertiesFilter = TreeUtils.makeNodeFilter(filterString); 168 this.updateSelectedTreeUiData(); 169 } 170 171 newPropertiesTree(selectedItem: HierarchyTreeNode) { 172 this.additionalPropertyEntry = null; 173 this.selectedHierarchyTree = selectedItem; 174 this.updateSelectedTreeUiData(); 175 } 176 177 newAdditionalPropertiesTree(selectedItem: any) { 178 this.selectedHierarchyTree = new HierarchyTreeNode( 179 selectedItem.name, 180 'AdditionalProperty', 181 'AdditionalProperty' 182 ); 183 this.additionalPropertyEntry = { 184 name: selectedItem.name, 185 kind: 'AdditionalProperty', 186 children: [], 187 stableId: 'AdditionalProperty', 188 proto: selectedItem.proto, 189 }; 190 this.updateSelectedTreeUiData(); 191 } 192 193 protected getAdditionalProperties( 194 wmEntry: TraceTreeNode | undefined, 195 sfEntry: TraceTreeNode | undefined 196 ) { 197 let wmProperties: ProcessedWindowManagerState | undefined; 198 let sfProperties: ImeLayers | undefined; 199 let sfSubtrees: any[]; 200 201 if (wmEntry) { 202 wmProperties = ImeUtils.processWindowManagerTraceEntry(wmEntry); 203 204 if (sfEntry) { 205 sfProperties = ImeUtils.getImeLayers(sfEntry, wmProperties); 206 sfSubtrees = [sfProperties?.taskOfImeContainer, sfProperties?.taskOfImeSnapshot] 207 .filter((node) => node) // filter away null values 208 .map((node) => { 209 node.kind = 'SF subtree - ' + node.id; 210 return node; 211 }); 212 this.entry?.children.push(...sfSubtrees); 213 } 214 } 215 216 return new ImeAdditionalProperties(wmProperties, sfProperties); 217 } 218 219 protected generateTree() { 220 if (!this.entry) { 221 return null; 222 } 223 224 const generator = new TreeGenerator(this.entry, this.hierarchyFilter, this.pinnedIds) 225 .setIsOnlyVisibleView(this.hierarchyUserOptions['onlyVisible']?.enabled) 226 .setIsSimplifyNames(this.hierarchyUserOptions['simplifyNames']?.enabled) 227 .setIsFlatView(this.hierarchyUserOptions['flat']?.enabled) 228 .withUniqueNodeId(); 229 const tree: HierarchyTreeNode | null = generator.generateTree(); 230 this.pinnedItems = generator.getPinnedItems(); 231 this.uiData.pinnedItems = this.pinnedItems; 232 return tree; 233 } 234 235 private updateSelectedTreeUiData() { 236 if (this.selectedHierarchyTree) { 237 this.uiData.propertiesTree = this.getTreeWithTransformedProperties( 238 this.selectedHierarchyTree 239 ); 240 } 241 this.notifyViewCallback(this.uiData); 242 } 243 private updatePinnedIds(newId: string) { 244 if (this.pinnedIds.includes(newId)) { 245 this.pinnedIds = this.pinnedIds.filter((pinned) => pinned !== newId); 246 } else { 247 this.pinnedIds.push(newId); 248 } 249 } 250 251 private getTreeWithTransformedProperties(selectedTree: HierarchyTreeNode): PropertiesTreeNode { 252 const transformer = new TreeTransformer(selectedTree, this.propertiesFilter) 253 .setOnlyProtoDump(this.additionalPropertyEntry != null) 254 .setIsShowDefaults(this.propertiesUserOptions['showDefaults']?.enabled) 255 .setTransformerOptions({skip: selectedTree.skip}) 256 .setProperties(this.additionalPropertyEntry ?? this.entry); 257 const transformedTree = transformer.transform(); 258 return transformedTree; 259 } 260 261 private findTraceEntries( 262 position: TracePosition 263 ): [ 264 TraceEntry<object> | undefined, 265 TraceEntry<LayerTraceEntry> | undefined, 266 TraceEntry<WindowManagerState> | undefined 267 ] { 268 const imeEntry = TraceEntryFinder.findCorrespondingEntry(this.imeTrace, position); 269 if (!imeEntry) { 270 return [undefined, undefined, undefined]; 271 } 272 273 if (!this.imeTrace.hasFrameInfo()) { 274 return [imeEntry, undefined, undefined]; 275 } 276 277 const frames = imeEntry.getFramesRange(); 278 if (!frames || frames.start === frames.end) { 279 return [imeEntry, undefined, undefined]; 280 } 281 282 const frame = frames.start; 283 const sfEntry = this.sfTrace?.getFrame(frame)?.findClosestEntry(imeEntry.getTimestamp()); 284 const wmEntry = this.wmTrace?.getFrame(frame)?.findClosestEntry(imeEntry.getTimestamp()); 285 286 return [imeEntry, sfEntry, wmEntry]; 287 } 288 289 protected abstract updateHierarchyTableProperties(): TableProperties; 290} 291