blob: 19d380c8e1b6479106449895c28e9e3af688116d [file] [log] [blame]
Vishnu Nair46babab2017-12-19 15:15:30 -08001<!-- Copyright (C) 2017 The Android Open Source Project
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14-->
15<template>
16 <div id="app">
17 <md-whiteframe md-tag="md-toolbar">
18 <h1 class="md-title" style="flex: 1">{{title}}</h1>
19
Nataniel Borges2f0f0882019-02-15 13:04:15 -080020 <div>
Nataniel Borges83d03e12019-02-27 14:29:54 -080021 <md-checkbox v-model="store.displayDefaults">Show default properties
22 <md-tooltip md-direction="bottom">
23 If checked, shows the value of all properties.
24 Otherwise, hides all properties whose value is the default for its data type.
25 </md-tooltip>
26 </md-checkbox>
Nataniel Borges2f0f0882019-02-15 13:04:15 -080027 </div>
28
Vishnu Nair46babab2017-12-19 15:15:30 -080029 <input type="file" @change="onLoadFile" id="upload-file" v-show="false"/>
30 <label class="md-button md-accent md-raised md-theme-default" for="upload-file">Open File</label>
31
32 <div>
33 <md-select v-model="fileType" id="file-type" placeholder="File type">
34 <md-option value="auto">Detect type</md-option>
35 <md-option :value="k" v-for="(v,k) in FILE_TYPES">{{v.name}}</md-option>
36 </md-select>
37 </div>
38
39 </md-whiteframe>
40
41 <div class="main-content" v-if="timeline.length">
42 <md-card class="timeline-card">
43 <md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense"><h2 class="md-title">Timeline</h2></md-whiteframe>
44 <timeline :items="timeline" :selected="tree" @item-selected="onTimelineItemSelected" class="timeline" />
45 </md-card>
46
47 <div class="container">
48 <md-card class="rects">
49 <md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense"><h2 class="md-title">Screen</h2></md-whiteframe>
50 <md-whiteframe md-elevation="8">
51 <rects :bounds="bounds" :rects="rects" :highlight="highlight" @rect-click="onRectClick" />
52 </md-whiteframe>
53 </md-card>
54
55 <md-card class="hierarchy">
56 <md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense">
57 <h2 class="md-title" style="flex: 1;">Hierarchy</h2>
58 <md-checkbox v-model="store.onlyVisible">Only visible</md-checkbox>
59 <md-checkbox v-model="store.flattened">Flat</md-checkbox>
60 </md-whiteframe>
61 <tree-view :item="tree" @item-selected="itemSelected" :selected="hierarchySelected" :filter="hierarchyFilter" :flattened="store.flattened" ref="hierarchy" />
62 </md-card>
63
64 <md-card class="properties">
65 <md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense">
66 <h2 class="md-title" style="flex: 1">Properties</h2>
67 <div class="filter">
68 <input id="filter" type="search" placeholder="Filter..." v-model="propertyFilterString" />
69 </div>
70 </md-whiteframe>
71 <tree-view :item="selectedTree" :filter="propertyFilter" />
72 </md-card>
73 </div>
74 </div>
75 </div>
76</template>
77
78<script>
79
Vishnu Nairf60b79f2017-12-28 11:18:46 -080080import jsonProtoDefs from 'frameworks/base/core/proto/android/server/windowmanagertrace.proto'
81import jsonProtoDefsSF from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto'
Vishnu Nair46babab2017-12-19 15:15:30 -080082import protobuf from 'protobufjs'
83
84import TreeView from './TreeView.vue'
85import Timeline from './Timeline.vue'
86import Rects from './Rects.vue'
87
88import detectFile from './detectfile.js'
89import LocalStore from './localstore.js'
90
91import {transform_json} from './transform.js'
92import {transform_layers, transform_layers_trace} from './transform_sf.js'
93import {transform_window_service, transform_window_trace} from './transform_wm.js'
Vishnu Nair8175a122019-03-06 13:37:40 -080094import {fill_transform_data, format_transform_type, is_simple_transform} from './matrix_utils.js'
Vishnu Nair46babab2017-12-19 15:15:30 -080095
96
97var protoDefs = protobuf.Root.fromJSON(jsonProtoDefs)
98 .addJSON(jsonProtoDefsSF.nested);
99
100var TraceMessage = protoDefs.lookupType(
Yi Jin84f33f52018-03-20 17:37:38 -0700101 "com.android.server.wm.WindowManagerTraceFileProto");
Vishnu Nair46babab2017-12-19 15:15:30 -0800102var ServiceMessage = protoDefs.lookupType(
Yi Jin84f33f52018-03-20 17:37:38 -0700103 "com.android.server.wm.WindowManagerServiceDumpProto");
Vishnu Nair46babab2017-12-19 15:15:30 -0800104var LayersMessage = protoDefs.lookupType("android.surfaceflinger.LayersProto");
105var LayersTraceMessage = protoDefs.lookupType("android.surfaceflinger.LayersTraceFileProto");
106
107function formatProto(obj) {
108 if (!obj || !obj.$type) {
109 return;
110 }
Vishnu Nair8175a122019-03-06 13:37:40 -0800111 if (obj.$type.fullName === '.android.surfaceflinger.RectProto' ||
112 obj.$type.fullName === '.android.graphics.RectProto') {
113 return `(${obj.left}, ${obj.top}) - (${obj.right}, ${obj.bottom})`;
114 } else if (obj.$type.fullName === '.android.surfaceflinger.FloatRectProto') {
115 return `(${obj.left.toFixed(3)}, ${obj.top.toFixed(3)}) - (${obj.right.toFixed(3)}, ${obj.bottom.toFixed(3)})`;
116 }
117 else if (obj.$type.fullName === '.android.surfaceflinger.PositionProto') {
118 return `(${obj.x.toFixed(3)}, ${obj.y.toFixed(3)})`;
Vishnu Nair46babab2017-12-19 15:15:30 -0800119 } else if (obj.$type.fullName === '.android.surfaceflinger.SizeProto') {
Vishnu Nair8175a122019-03-06 13:37:40 -0800120 return `${obj.w} x ${obj.h}`;
Vishnu Nair46babab2017-12-19 15:15:30 -0800121 } else if (obj.$type.fullName === '.android.surfaceflinger.ColorProto') {
Vishnu Nair8175a122019-03-06 13:37:40 -0800122 return `r:${obj.r} g:${obj.g} \n b:${obj.b} a:${obj.a}`;
123 } else if (obj.$type.fullName === '.android.surfaceflinger.TransformProto') {
124 var transform_type = format_transform_type(obj);
125 if (is_simple_transform(obj)) {
126 return `${transform_type}`;
127 }
128 return `${transform_type} dsdx:${obj.dsdx.toFixed(3)} dtdx:${obj.dtdx.toFixed(3)} dsdy:${obj.dsdy.toFixed(3)} dtdy:${obj.dtdy.toFixed(3)}`;
Vishnu Nair46babab2017-12-19 15:15:30 -0800129 }
130}
131
132const FILE_TYPES = {
133 'window_dump': {
134 protoType: ServiceMessage,
135 transform: transform_window_service,
136 name: "WindowManager dump",
137 timeline: false,
138 },
139 'window_trace': {
140 protoType: TraceMessage,
141 transform: transform_window_trace,
142 name: "WindowManager trace",
143 timeline: true,
144 },
145 'layers_dump': {
146 protoType: LayersMessage,
147 transform: transform_layers,
148 name: "SurfaceFlinger dump",
149 timeline: false,
150 },
151 'layers_trace': {
152 protoType: LayersTraceMessage,
153 transform: transform_layers_trace,
154 name: "SurfaceFlinger trace",
155 timeline: true,
156 },
157};
158
159export default {
160 name: 'app',
161 data() {
162 return {
163 selectedTree: {},
164 hierarchySelected: null,
165 tree: {},
166 timeline: [],
167 bounds: {},
168 rects: [],
169 highlight: null,
170 timelineIndex: 0,
171 title: "The Tool",
172 filename: "",
173 lastSelectedStableId: null,
174 propertyFilterString: "",
175 store: LocalStore('app', {
176 flattened: false,
177 onlyVisible: false,
Nataniel Borges2f0f0882019-02-15 13:04:15 -0800178 displayDefaults: true
Vishnu Nair46babab2017-12-19 15:15:30 -0800179 }),
180 FILE_TYPES,
181 fileType: "auto",
182 }
183 },
184 created() {
185 window.addEventListener('keydown', this.onKeyDown);
186 document.title = this.title;
187 },
188 methods: {
189 onLoadFile(e) {
190 return this.onLoadProtoFile(e, this.fileType);
191 },
192 onLoadProtoFile(event, type) {
193 var files = event.target.files || event.dataTransfer.files;
194 var file = files[0];
195 if (!file) {
196 // No file selected.
197 return;
198 }
199 this.filename = file.name;
200 this.title = this.filename + " (loading)";
201
202 var reader = new FileReader();
203 reader.onload = (e) => {
204 var buffer = new Uint8Array(e.target.result);
205 var filetype = FILE_TYPES[type] || FILE_TYPES[detectFile(buffer)];
206 if (!filetype) {
207 this.title = this.filename + ": Could not detect file type."
208 event.target.value = '';
209 return;
210 }
211 this.title = this.filename + " (loading " + filetype.name + ")";
212
Vishnu Nair8175a122019-03-06 13:37:40 -0800213 // Replace enum values with string representation and
214 // add default values to the proto objects. This function also handles
215 // a special case with TransformProtos where the matrix may be derived
216 // from the transform type.
217 function modifyProtoFields(protoObj, displayDefaults) {
218 if (!protoObj || protoObj !== Object(protoObj) || !protoObj.$type) {
219 return;
220 }
221 for (var fieldName in protoObj.$type.fields) {
222 var fieldProperties = protoObj.$type.fields[fieldName];
223 var field = protoObj[fieldName];
224
225 if (Array.isArray(field)) {
226 field.forEach((item, _) => {
227 modifyProtoFields(item, displayDefaults);
228 })
229 continue;
230 }
231
232 if (displayDefaults && !(field)) {
233 protoObj[fieldName] = fieldProperties.defaultValue;
234 }
235
236 if (fieldProperties.type === 'TransformProto'){
237 fill_transform_data(protoObj[fieldName]);
Vishnu Nair13f8d142019-03-19 17:10:48 -0700238 continue;
Vishnu Nair8175a122019-03-06 13:37:40 -0800239 }
240
241 if (fieldProperties.resolvedType && fieldProperties.resolvedType.valuesById) {
242 protoObj[fieldName] = fieldProperties.resolvedType.valuesById[protoObj[fieldProperties.name]];
243 continue;
244 }
245
246 modifyProtoFields(protoObj[fieldName], displayDefaults);
247 }
248 }
249
Vishnu Nair46babab2017-12-19 15:15:30 -0800250 try {
251 var decoded = filetype.protoType.decode(buffer);
Vishnu Nair8175a122019-03-06 13:37:40 -0800252 modifyProtoFields(decoded, this.store.displayDefaults);
Vishnu Nair46babab2017-12-19 15:15:30 -0800253 var transformed = filetype.transform(decoded);
254 } catch (ex) {
255 this.title = this.filename + " (loading " + filetype.name + "):" + ex;
256 return;
257 } finally {
258 event.target.value = '';
259 }
260
261 if (filetype.timeline) {
262 this.timeline = transformed.children;
263 } else {
264 this.timeline = [transformed];
265 }
266
267 this.title = this.filename + " (" + filetype.name + ")";
268
269 this.lastSelectedStableId = null;
270 this.onTimelineItemSelected(this.timeline[0], 0);
271 }
272 reader.readAsArrayBuffer(files[0]);
273 },
274 itemSelected(item) {
275 this.hierarchySelected = item;
276 this.selectedTree = transform_json(item.obj, item.name, {
277 skip: item.skip,
278 formatter: formatProto});
279 this.highlight = item.highlight;
280 this.lastSelectedStableId = item.stableId;
281 },
282 onRectClick(item) {
283 if (item) {
284 this.itemSelected(item);
285 }
286 },
287 onTimelineItemSelected(item, index) {
288 this.timelineIndex = index;
289 this.tree = item;
290 this.rects = [...item.rects].reverse();
291 this.bounds = item.bounds;
292
293 this.hierarchySelected = null;
294 this.selectedTree = {};
295 this.highlight = null;
296
297 function find_item(item, stableId) {
298 if (item.stableId === stableId) {
299 return item;
300 }
301 if (Array.isArray(item.children)) {
302 for (var child of item.children) {
303 var found = find_item(child, stableId);
304 if (found) {
305 return found;
306 }
307 }
308 }
309 return null;
310 }
311
312 if (this.lastSelectedStableId) {
313 var found = find_item(item, this.lastSelectedStableId);
314 if (found) {
315 this.itemSelected(found);
316 }
317 }
318 },
319 onKeyDown(event) {
320 event = event || window.event;
321 if (event.keyCode == 37 /* left */) {
322 this.advanceTimeline(-1);
323 } else if (event.keyCode == 39 /* right */) {
324 this.advanceTimeline(1);
325 } else if (event.keyCode == 38 /* up */) {
326 this.$refs.hierarchy.selectPrev();
327 } else if (event.keyCode == 40 /* down */) {
328 this.$refs.hierarchy.selectNext();
329 } else {
330 return false;
331 }
332 event.preventDefault();
333 return true;
334 },
335 advanceTimeline(frames) {
336 if (!Array.isArray(this.timeline) || this.timeline.length == 0) {
337 return false;
338 }
339 var nextIndex = this.timelineIndex + frames;
340 if (nextIndex < 0) {
341 nextIndex = 0;
342 }
343 if (nextIndex >= this.timeline.length) {
344 nextIndex = this.timeline.length - 1;
345 }
346 this.onTimelineItemSelected(this.timeline[nextIndex], nextIndex);
347 return true;
348 },
349 },
350 computed: {
351 prettyDump: function() { return JSON.stringify(this.dump, null, 2); },
352 hierarchyFilter() {
353 return this.store.onlyVisible ? (c, flattened) => {
354 return c.visible || c.childrenVisible && !flattened;
355 } : null;
356 },
357 propertyFilter() {
358 var filterStrings = this.propertyFilterString.split(",");
359 var positive = [];
360 var negative = [];
361 filterStrings.forEach((f) => {
362 if (f.startsWith("!")) {
363 var str = f.substring(1);
364 negative.push((s) => s.indexOf(str) === -1);
365 } else {
366 var str = f;
367 positive.push((s) => s.indexOf(str) !== -1);
368 }
369 });
370 var filter = (item) => {
371 var apply = (f) => f(item.name);
372 return (positive.length === 0 || positive.some(apply))
373 && (negative.length === 0 || negative.every(apply));
374 };
375 filter.includeChildren = true;
376 return filter;
377 },
378 },
379 watch: {
380 title() {
381 document.title = this.title;
382 }
383 },
384 components: {
385 'tree-view': TreeView,
386 'timeline': Timeline,
387 'rects': Rects,
388 }
389}
390</script>
391
392<style>
393#app {
394}
395
396.main-content {
397 padding: 8px;
398}
399
400.card-toolbar {
401 border-bottom: 1px solid rgba(0, 0, 0, .12);
402}
403
404.timeline-card {
405 margin: 8px;
406}
407
408.timeline {
409 margin: 16px;
410}
411
412.screen {
413 border: 1px solid black;
414}
415
416.container {
417 display: flex;
418 flex-wrap: wrap;
419}
420
421.rects {
422 flex: none;
423 margin: 8px;
424}
425
426.hierarchy, .properties {
427 flex: 1;
428 margin: 8px;
429 min-width: 400px;
430}
431
432.hierarchy > .tree-view, .properties > .tree-view {
433 margin: 16px;
434}
435
436h1, h2 {
437 font-weight: normal;
438}
439
440ul {
441 list-style-type: none;
442 padding: 0;
443}
444
445li {
446 display: inline-block;
447 margin: 0 10px;
448}
449
450a {
451 color: #42b983;
452}
453</style>