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 {ParserTimestampConverter} from 'common/timestamp_converter'; 19import {AddDefaults} from 'parsers/operations/add_defaults'; 20import {SetFormatters} from 'parsers/operations/set_formatters'; 21import {AbstractParser} from 'parsers/perfetto/abstract_parser'; 22import {FakeProtoTransformer} from 'parsers/perfetto/fake_proto_transformer'; 23import {Utils} from 'parsers/perfetto/utils'; 24import {TamperedMessageType} from 'parsers/tampered_message_type'; 25import {RectsComputation} from 'parsers/view_capture/computations/rects_computation'; 26import {VisibilityComputation} from 'parsers/view_capture/computations/visibility_computation'; 27import root from 'protos/viewcapture/latest/json'; 28import {perfetto} from 'protos/viewcapture/latest/static'; 29import { 30 CustomQueryParserResultTypeMap, 31 CustomQueryType, 32 VisitableParserCustomQuery, 33} from 'trace/custom_query'; 34import {EntriesRange} from 'trace/index_types'; 35import {TraceFile} from 'trace/trace_file'; 36import {TraceType} from 'trace/trace_type'; 37import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node'; 38import {PropertiesProvider} from 'trace/tree_node/properties_provider'; 39import {PropertiesProviderBuilder} from 'trace/tree_node/properties_provider_builder'; 40import {PropertyTreeBuilderFromProto} from 'trace/tree_node/property_tree_builder_from_proto'; 41import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 42import {WasmEngineProxy} from 'trace_processor/wasm_engine_proxy'; 43import {HierarchyTreeBuilderVc} from './hierarchy_tree_builder_vc'; 44 45export class ParserViewCaptureWindow extends AbstractParser<HierarchyTreeNode> { 46 private static readonly PROTO_WRAPPER_MESSAGE = TamperedMessageType.tamper( 47 root.lookupType('perfetto.protos.Wrapper'), 48 ); 49 private static readonly PROTO_VIEWCAPTURE_FIELD = 50 ParserViewCaptureWindow.PROTO_WRAPPER_MESSAGE.fields['viewcapture']; 51 private static readonly PROTO_VIEW_FIELD = assertDefined( 52 ParserViewCaptureWindow.PROTO_VIEWCAPTURE_FIELD.tamperedMessageType?.fields[ 53 'views' 54 ], 55 ); 56 57 private static readonly PROPERTY_TREE_OPERATIONS = [ 58 new AddDefaults(ParserViewCaptureWindow.PROTO_VIEW_FIELD), 59 new SetFormatters(ParserViewCaptureWindow.PROTO_VIEW_FIELD), 60 ]; 61 62 private readonly packageName: string; 63 private readonly windowName: string; 64 private readonly protoTransformer: FakeProtoTransformer; 65 66 constructor( 67 traceFile: TraceFile, 68 traceProcessor: WasmEngineProxy, 69 timestampConverter: ParserTimestampConverter, 70 packageName: string, 71 windowName: string, 72 ) { 73 super(traceFile, traceProcessor, timestampConverter); 74 75 this.packageName = packageName; 76 this.windowName = windowName; 77 78 this.protoTransformer = new FakeProtoTransformer( 79 assertDefined( 80 ParserViewCaptureWindow.PROTO_VIEWCAPTURE_FIELD.tamperedMessageType, 81 ), 82 ); 83 } 84 85 override getTraceType(): TraceType { 86 return TraceType.VIEW_CAPTURE; 87 } 88 89 override async getEntry(index: number): Promise<HierarchyTreeNode> { 90 let entry = (await Utils.queryEntry( 91 this.traceProcessor, 92 this.getTableName(), 93 this.entryIndexToRowIdMap, 94 index, 95 )) as perfetto.protos.IViewCapture; 96 entry = this.protoTransformer.transform(entry); 97 98 const views = this.makeViewPropertyProviders(entry); 99 100 const rootView = assertDefined( 101 views.find((view) => { 102 const parentId = assertDefined( 103 view.getEagerProperties().getChildByName('parentId'), 104 ).getValue() as number; 105 return parentId === -1; 106 }), 107 ); 108 109 const childrenViews = views.filter((view) => view !== rootView); 110 111 return new HierarchyTreeBuilderVc() 112 .setRoot(rootView) 113 .setChildren(childrenViews) 114 .setComputations([new VisibilityComputation(), new RectsComputation()]) 115 .build(); 116 } 117 118 override customQuery<Q extends CustomQueryType>( 119 type: Q, 120 entriesRange: EntriesRange, 121 ): Promise<CustomQueryParserResultTypeMap[Q]> { 122 return new VisitableParserCustomQuery(type) 123 .visit(CustomQueryType.VIEW_CAPTURE_METADATA, async () => { 124 const metadata = { 125 packageName: this.packageName, 126 windowName: this.windowName, 127 }; 128 return Promise.resolve(metadata); 129 }) 130 .getResult(); 131 } 132 133 protected override getStdLibModuleName(): string | undefined { 134 return 'android.winscope.viewcapture'; 135 } 136 137 protected override getTableName(): string { 138 return 'android_viewcapture'; 139 } 140 141 override async buildEntryIndexToRowIdMap(): Promise<number[]> { 142 const sqlRowIdAndTimestamp = ` 143 SELECT vc.id as id, vc.ts as ts 144 FROM ${this.getTableName()} AS vc 145 JOIN args ON vc.arg_set_id = args.arg_set_id 146 WHERE 147 args.key = 'window_name' AND 148 args.string_value = '${this.windowName}' 149 ORDER BY vc.ts; 150 `; 151 const result = await this.traceProcessor 152 .query(sqlRowIdAndTimestamp) 153 .waitAllRows(); 154 const entryIndexToRowId: number[] = []; 155 for (const it = result.iter({}); it.valid(); it.next()) { 156 const rowId = Number(it.get('id') as bigint); 157 entryIndexToRowId.push(rowId); 158 } 159 return entryIndexToRowId; 160 } 161 162 private makeViewPropertyProviders( 163 entry: perfetto.protos.IViewCapture, 164 ): PropertiesProvider[] { 165 const providers = (entry.views ?? []).map((view) => { 166 const allProperties = this.makeViewPropertyTree(view); 167 const provider = new PropertiesProviderBuilder() 168 .setEagerProperties(allProperties) 169 .setCommonOperations(ParserViewCaptureWindow.PROPERTY_TREE_OPERATIONS) 170 .build(); 171 172 return provider; 173 }); 174 175 return providers; 176 } 177 178 private makeViewPropertyTree( 179 view: perfetto.protos.ViewCapture.IView, 180 ): PropertyTreeNode { 181 const rootName = `${(view as any).className}@${view.hashcode}`; 182 183 const nodeProperties = new PropertyTreeBuilderFromProto() 184 .setData(view) 185 .setRootId('root-view') 186 .setRootName(rootName) 187 .build(); 188 189 return nodeProperties; 190 } 191} 192