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