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, assertTrue} from 'common/assert_utils'; 18import {PersistentStoreProxy} from 'common/persistent_store_proxy'; 19import { 20 TabbedViewSwitchRequest, 21 WinscopeEvent, 22 WinscopeEventType, 23} from 'messaging/winscope_event'; 24import {CustomQueryType} from 'trace/custom_query'; 25import {Trace} from 'trace/trace'; 26import {Traces} from 'trace/traces'; 27import {TraceEntryFinder} from 'trace/trace_entry_finder'; 28import {TraceType} from 'trace/trace_type'; 29import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node'; 30import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 31import { 32 AbstractHierarchyViewerPresenter, 33 NotifyHierarchyViewCallbackType, 34} from 'viewers/common/abstract_hierarchy_viewer_presenter'; 35import {VISIBLE_CHIP} from 'viewers/common/chip'; 36import {VcCuratedProperties} from 'viewers/common/curated_properties'; 37import {DisplayIdentifier} from 'viewers/common/display_identifier'; 38import {HierarchyPresenter} from 'viewers/common/hierarchy_presenter'; 39import {PropertiesPresenter} from 'viewers/common/properties_presenter'; 40import {RectsPresenter} from 'viewers/common/rects_presenter'; 41import {UiHierarchyTreeNode} from 'viewers/common/ui_hierarchy_tree_node'; 42import {UI_RECT_FACTORY} from 'viewers/common/ui_rect_factory'; 43import {UserOptions} from 'viewers/common/user_options'; 44import {UiRect} from 'viewers/components/rects/types2d'; 45import {UiData} from './ui_data'; 46 47export class Presenter extends AbstractHierarchyViewerPresenter { 48 static readonly DENYLIST_PROPERTY_NAMES = ['children', 'isComputedVisible']; 49 50 private windowNames: string[] = []; 51 protected override hierarchyPresenter = new HierarchyPresenter( 52 PersistentStoreProxy.new<UserOptions>( 53 'VcHierarchyOptions', 54 { 55 showDiff: { 56 name: 'Show diff', 57 enabled: false, 58 isUnavailable: false, 59 }, 60 showOnlyVisible: { 61 name: 'Show only', 62 chip: VISIBLE_CHIP, 63 enabled: false, 64 }, 65 simplifyNames: { 66 name: 'Simplify names', 67 enabled: true, 68 }, 69 }, 70 this.storage, 71 ), 72 Presenter.DENYLIST_PROPERTY_NAMES, 73 false, 74 true, 75 ); 76 protected override rectsPresenter = new RectsPresenter( 77 PersistentStoreProxy.new<UserOptions>( 78 'VcRectsOptions', 79 { 80 ignoreNonHidden: { 81 name: 'Ignore', 82 icon: 'visibility', 83 enabled: false, 84 }, 85 showOnlyVisible: { 86 name: 'Show only', 87 chip: VISIBLE_CHIP, 88 enabled: false, 89 }, 90 }, 91 this.storage, 92 ), 93 (tree: HierarchyTreeNode, trace: Trace<HierarchyTreeNode>) => 94 UI_RECT_FACTORY.makeVcUiRects( 95 tree, 96 this.getIdFromViewCaptureTrace(trace), 97 ), 98 ); 99 protected override propertiesPresenter = new PropertiesPresenter( 100 PersistentStoreProxy.new<UserOptions>( 101 'VcPropertyOptions', 102 { 103 showDiff: { 104 name: 'Show diff', 105 enabled: false, 106 isUnavailable: false, 107 }, 108 showDefaults: { 109 name: 'Show defaults', 110 enabled: false, 111 tooltip: ` 112 If checked, shows the value of all properties. 113 Otherwise, hides all properties whose value is 114 the default for its data type. 115 `, 116 }, 117 }, 118 this.storage, 119 ), 120 Presenter.DENYLIST_PROPERTY_NAMES, 121 ); 122 protected override readonly multiTraceType = TraceType.VIEW_CAPTURE; 123 124 private readonly surfaceFlingerTrace: Trace<HierarchyTreeNode> | undefined; 125 private readonly viewCaptureTraces: Array<Trace<HierarchyTreeNode>>; 126 127 private viewCapturePackageNames: string[] = []; 128 private sfRects: UiRect[] | undefined; 129 private curatedProperties: VcCuratedProperties | undefined; 130 131 constructor( 132 traces: Traces, 133 storage: Readonly<Storage>, 134 notifyViewCallback: NotifyHierarchyViewCallbackType, 135 ) { 136 super(undefined, traces, storage, notifyViewCallback, new UiData()); 137 this.viewCaptureTraces = traces.getTraces(TraceType.VIEW_CAPTURE); 138 this.surfaceFlingerTrace = traces.getTrace(TraceType.SURFACE_FLINGER); 139 } 140 141 async onMiniRectsDoubleClick() { 142 if (!this.surfaceFlingerTrace) { 143 return; 144 } 145 await this.emitWinscopeEvent( 146 new TabbedViewSwitchRequest(this.surfaceFlingerTrace), 147 ); 148 } 149 150 getTraces(): Array<Trace<HierarchyTreeNode>> { 151 return this.viewCaptureTraces; 152 } 153 154 getViewCaptureTraceFromId(id: number): Trace<HierarchyTreeNode> { 155 return assertDefined(this.viewCaptureTraces[id]); 156 } 157 158 override async onAppEvent(event: WinscopeEvent) { 159 await event.visit( 160 WinscopeEventType.TRACE_POSITION_UPDATE, 161 async (event) => { 162 await this.initializeIfNeeded(); 163 await this.applyTracePositionUpdate(event); 164 165 if (this.uiData && this.surfaceFlingerTrace) { 166 const surfaceFlingerEntry = 167 (await TraceEntryFinder.findCorrespondingEntry( 168 this.surfaceFlingerTrace, 169 event.position, 170 )?.getValue()) as HierarchyTreeNode; 171 if (surfaceFlingerEntry) { 172 this.sfRects = UI_RECT_FACTORY.makeUiRects( 173 surfaceFlingerEntry, 174 this.viewCapturePackageNames, 175 ); 176 } 177 } 178 this.updateCuratedProperties(); 179 this.refreshUIData(); 180 }, 181 ); 182 } 183 184 override async onHighlightedNodeChange(node: UiHierarchyTreeNode) { 185 await this.applyHighlightedNodeChange(node); 186 this.updateCuratedProperties(); 187 this.refreshUIData(); 188 } 189 190 override async onHighlightedIdChange(newId: string) { 191 await this.applyHighlightedIdChange(newId); 192 this.updateCuratedProperties(); 193 this.refreshUIData(); 194 } 195 196 protected override getOverrideDisplayName(): undefined { 197 return undefined; 198 } 199 200 protected override keepCalculated(): boolean { 201 return true; 202 } 203 204 private async initializeIfNeeded() { 205 await this.initializePackageNamesIfNeeded(); 206 await this.initializeWindowsIfNeeded(); 207 } 208 209 private async initializePackageNamesIfNeeded() { 210 if (this.viewCapturePackageNames.length > 0) { 211 return; 212 } 213 214 const promisesPackageName = this.viewCaptureTraces.map(async (trace) => { 215 const packageAndWindow = await trace.customQuery( 216 CustomQueryType.VIEW_CAPTURE_METADATA, 217 ); 218 return packageAndWindow.packageName; 219 }); 220 221 this.viewCapturePackageNames = await Promise.all(promisesPackageName); 222 } 223 224 private async initializeWindowsIfNeeded() { 225 if (this.rectsPresenter.getDisplays().length > 0) { 226 return; 227 } 228 229 const shortenAndCapitalizeWindowName = (name: string) => { 230 const lastDot = name.lastIndexOf('.'); 231 if (lastDot !== -1) { 232 name = name.substring(lastDot + 1); 233 } 234 if (name.length > 0) { 235 name = name[0].toUpperCase() + name.slice(1); 236 } 237 return name; 238 }; 239 240 const promisesWindowName = this.viewCaptureTraces.map(async (trace) => { 241 const packageAndWindow = await trace.customQuery( 242 CustomQueryType.VIEW_CAPTURE_METADATA, 243 ); 244 return shortenAndCapitalizeWindowName(packageAndWindow.windowName); 245 }); 246 this.windowNames = await Promise.all(promisesWindowName); 247 this.rectsPresenter.setDisplays(this.getWindows(this.windowNames)); 248 } 249 250 private getWindows(windowNames: string[]): DisplayIdentifier[] { 251 return this.viewCaptureTraces 252 .map((trace, i) => { 253 const traceId = this.getIdFromViewCaptureTrace(trace); 254 return { 255 displayId: traceId, 256 groupId: traceId, 257 name: windowNames[i], 258 }; 259 }) 260 .sort((a, b) => a.name.localeCompare(b.name)); 261 } 262 263 private updateCuratedProperties() { 264 const propertiesTree = this.propertiesPresenter.getPropertiesTree(); 265 if (propertiesTree) { 266 this.curatedProperties = this.getCuratedProperties(propertiesTree); 267 } else { 268 this.curatedProperties = undefined; 269 } 270 } 271 272 private getCuratedProperties(tree: PropertyTreeNode): VcCuratedProperties { 273 const curated: VcCuratedProperties = { 274 className: tree.name, 275 hashcode: assertDefined(tree.getChildByName('hashcode')).formattedValue(), 276 left: assertDefined(tree.getChildByName('left')).formattedValue(), 277 top: assertDefined(tree.getChildByName('top')).formattedValue(), 278 elevation: assertDefined( 279 tree.getChildByName('elevation'), 280 ).formattedValue(), 281 height: assertDefined(tree.getChildByName('height')).formattedValue(), 282 width: assertDefined(tree.getChildByName('width')).formattedValue(), 283 translationX: assertDefined( 284 tree.getChildByName('translationX'), 285 ).formattedValue(), 286 translationY: assertDefined( 287 tree.getChildByName('translationY'), 288 ).formattedValue(), 289 scrollX: assertDefined(tree.getChildByName('scrollX')).formattedValue(), 290 scrollY: assertDefined(tree.getChildByName('scrollY')).formattedValue(), 291 scaleX: assertDefined(tree.getChildByName('scaleX')).formattedValue(), 292 scaleY: assertDefined(tree.getChildByName('scaleY')).formattedValue(), 293 visibility: assertDefined( 294 tree.getChildByName('visibility'), 295 ).formattedValue(), 296 alpha: assertDefined(tree.getChildByName('alpha')).formattedValue(), 297 willNotDraw: assertDefined( 298 tree.getChildByName('willNotDraw'), 299 ).formattedValue(), 300 clipChildren: assertDefined( 301 tree.getChildByName('clipChildren'), 302 ).formattedValue(), 303 }; 304 return curated; 305 } 306 307 private getIdFromViewCaptureTrace(trace: Trace<HierarchyTreeNode>): number { 308 const index = this.viewCaptureTraces.indexOf(trace); 309 assertTrue(index !== -1); 310 return index; 311 } 312 313 private refreshUIData() { 314 this.refreshHierarchyViewerUiData( 315 new UiData(this.sfRects, this.curatedProperties), 316 ); 317 } 318} 319