• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<!-- 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
20      <input type="file" @change="onLoadFile" id="upload-file" v-show="false"/>
21      <label class="md-button md-accent md-raised md-theme-default" for="upload-file">Open File</label>
22
23      <div>
24        <md-select v-model="fileType" id="file-type" placeholder="File type">
25          <md-option value="auto">Detect type</md-option>
26          <md-option :value="k" v-for="(v,k) in FILE_TYPES">{{v.name}}</md-option>
27        </md-select>
28      </div>
29
30    </md-whiteframe>
31
32    <div class="main-content" v-if="timeline.length">
33      <md-card class="timeline-card">
34        <md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense"><h2 class="md-title">Timeline</h2></md-whiteframe>
35        <timeline :items="timeline" :selected="tree" @item-selected="onTimelineItemSelected" class="timeline" />
36      </md-card>
37
38      <div class="container">
39        <md-card class="rects">
40          <md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense"><h2 class="md-title">Screen</h2></md-whiteframe>
41          <md-whiteframe md-elevation="8">
42            <rects :bounds="bounds" :rects="rects" :highlight="highlight" @rect-click="onRectClick" />
43          </md-whiteframe>
44        </md-card>
45
46        <md-card class="hierarchy">
47          <md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense">
48            <h2 class="md-title" style="flex: 1;">Hierarchy</h2>
49            <md-checkbox v-model="store.onlyVisible">Only visible</md-checkbox>
50            <md-checkbox v-model="store.flattened">Flat</md-checkbox>
51          </md-whiteframe>
52          <tree-view :item="tree" @item-selected="itemSelected" :selected="hierarchySelected" :filter="hierarchyFilter" :flattened="store.flattened" ref="hierarchy" />
53        </md-card>
54
55        <md-card class="properties">
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">Properties</h2>
58            <div class="filter">
59              <input id="filter" type="search" placeholder="Filter..." v-model="propertyFilterString" />
60            </div>
61          </md-whiteframe>
62          <tree-view :item="selectedTree" :filter="propertyFilter" />
63        </md-card>
64      </div>
65    </div>
66  </div>
67</template>
68
69<script>
70
71import jsonProtoDefs from 'frameworks/base/core/proto/android/server/windowmanagertrace.proto'
72import jsonProtoDefsSF from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto'
73import protobuf from 'protobufjs'
74
75import TreeView from './TreeView.vue'
76import Timeline from './Timeline.vue'
77import Rects from './Rects.vue'
78
79import detectFile from './detectfile.js'
80import LocalStore from './localstore.js'
81
82import {transform_json} from './transform.js'
83import {transform_layers, transform_layers_trace} from './transform_sf.js'
84import {transform_window_service, transform_window_trace} from './transform_wm.js'
85
86
87var protoDefs = protobuf.Root.fromJSON(jsonProtoDefs)
88    .addJSON(jsonProtoDefsSF.nested);
89
90var TraceMessage = protoDefs.lookupType(
91  "com.android.server.wm.WindowManagerTraceFileProto");
92var ServiceMessage = protoDefs.lookupType(
93  "com.android.server.wm.WindowManagerServiceDumpProto");
94var LayersMessage = protoDefs.lookupType("android.surfaceflinger.LayersProto");
95var LayersTraceMessage = protoDefs.lookupType("android.surfaceflinger.LayersTraceFileProto");
96
97function formatProto(obj) {
98  if (!obj || !obj.$type) {
99    return;
100  }
101  if (obj.$type.fullName === '.android.surfaceflinger.RectProto') {
102    return `(${obj.left},${obj.top})-(${obj.right},${obj.bottom})`;
103  } else if (obj.$type.fullName === '.android.surfaceflinger.PositionProto') {
104    return `(${obj.x},${obj.y})`;
105  } else if (obj.$type.fullName === '.android.surfaceflinger.SizeProto') {
106    return `${obj.w}x${obj.h}`;
107  } else if (obj.$type.fullName === '.android.surfaceflinger.ColorProto') {
108    return `r:${obj.r} g:${obj.g} b:${obj.b} a:${obj.a}`;
109  }
110}
111
112const FILE_TYPES = {
113  'window_dump': {
114    protoType: ServiceMessage,
115    transform: transform_window_service,
116    name: "WindowManager dump",
117    timeline: false,
118  },
119  'window_trace': {
120    protoType: TraceMessage,
121    transform: transform_window_trace,
122    name: "WindowManager trace",
123    timeline: true,
124  },
125  'layers_dump': {
126    protoType: LayersMessage,
127    transform: transform_layers,
128    name: "SurfaceFlinger dump",
129    timeline: false,
130  },
131  'layers_trace': {
132    protoType: LayersTraceMessage,
133    transform: transform_layers_trace,
134    name: "SurfaceFlinger trace",
135    timeline: true,
136  },
137};
138
139export default {
140  name: 'app',
141  data() {
142    return {
143      selectedTree: {},
144      hierarchySelected: null,
145      tree: {},
146      timeline: [],
147      bounds: {},
148      rects: [],
149      highlight: null,
150      timelineIndex: 0,
151      title: "The Tool",
152      filename: "",
153      lastSelectedStableId: null,
154      propertyFilterString: "",
155      store: LocalStore('app', {
156        flattened: false,
157        onlyVisible: false,
158      }),
159      FILE_TYPES,
160      fileType: "auto",
161    }
162  },
163  created() {
164    window.addEventListener('keydown', this.onKeyDown);
165    document.title = this.title;
166  },
167  methods: {
168    onLoadFile(e) {
169      return this.onLoadProtoFile(e, this.fileType);
170    },
171    onLoadProtoFile(event, type) {
172      var files = event.target.files || event.dataTransfer.files;
173      var file = files[0];
174      if (!file) {
175        // No file selected.
176        return;
177      }
178      this.filename = file.name;
179      this.title = this.filename + " (loading)";
180
181      var reader = new FileReader();
182      reader.onload = (e) => {
183        var buffer = new Uint8Array(e.target.result);
184        var filetype = FILE_TYPES[type] || FILE_TYPES[detectFile(buffer)];
185        if (!filetype) {
186          this.title = this.filename + ": Could not detect file type."
187          event.target.value = '';
188          return;
189        }
190        this.title = this.filename + " (loading " + filetype.name + ")";
191
192        try {
193          var decoded = filetype.protoType.decode(buffer);
194          decoded = filetype.protoType.toObject(decoded, {enums: String, defaults: true});
195          var transformed = filetype.transform(decoded);
196        } catch (ex) {
197          this.title = this.filename + " (loading " + filetype.name + "):" + ex;
198          return;
199        } finally {
200          event.target.value = '';
201        }
202
203        if (filetype.timeline) {
204          this.timeline = transformed.children;
205        } else {
206          this.timeline = [transformed];
207        }
208
209        this.title = this.filename + " (" + filetype.name + ")";
210
211        this.lastSelectedStableId = null;
212        this.onTimelineItemSelected(this.timeline[0], 0);
213      }
214      reader.readAsArrayBuffer(files[0]);
215    },
216    itemSelected(item) {
217      this.hierarchySelected = item;
218      this.selectedTree = transform_json(item.obj, item.name, {
219          skip: item.skip,
220          formatter: formatProto});
221      this.highlight = item.highlight;
222      this.lastSelectedStableId = item.stableId;
223    },
224    onRectClick(item) {
225      if (item) {
226        this.itemSelected(item);
227      }
228    },
229    onTimelineItemSelected(item, index) {
230      this.timelineIndex = index;
231      this.tree = item;
232      this.rects = [...item.rects].reverse();
233      this.bounds = item.bounds;
234
235      this.hierarchySelected = null;
236      this.selectedTree = {};
237      this.highlight = null;
238
239      function find_item(item, stableId) {
240        if (item.stableId === stableId) {
241          return item;
242        }
243        if (Array.isArray(item.children)) {
244          for (var child of item.children) {
245            var found = find_item(child, stableId);
246            if (found) {
247              return found;
248            }
249          }
250        }
251        return null;
252      }
253
254      if (this.lastSelectedStableId) {
255        var found = find_item(item, this.lastSelectedStableId);
256        if (found) {
257          this.itemSelected(found);
258        }
259      }
260    },
261    onKeyDown(event) {
262      event = event || window.event;
263      if (event.keyCode == 37 /* left */) {
264        this.advanceTimeline(-1);
265      } else if (event.keyCode == 39 /* right */) {
266        this.advanceTimeline(1);
267      } else if (event.keyCode == 38 /* up */) {
268        this.$refs.hierarchy.selectPrev();
269      } else if (event.keyCode == 40 /* down */) {
270        this.$refs.hierarchy.selectNext();
271      } else {
272        return false;
273      }
274      event.preventDefault();
275      return true;
276    },
277    advanceTimeline(frames) {
278      if (!Array.isArray(this.timeline) || this.timeline.length == 0) {
279        return false;
280      }
281      var nextIndex = this.timelineIndex + frames;
282      if (nextIndex < 0) {
283        nextIndex = 0;
284      }
285      if (nextIndex >= this.timeline.length) {
286        nextIndex = this.timeline.length - 1;
287      }
288      this.onTimelineItemSelected(this.timeline[nextIndex], nextIndex);
289      return true;
290    },
291  },
292  computed: {
293    prettyDump: function() { return JSON.stringify(this.dump, null, 2); },
294    hierarchyFilter() {
295      return this.store.onlyVisible ? (c, flattened) => {
296        return c.visible || c.childrenVisible && !flattened;
297      } : null;
298    },
299    propertyFilter() {
300      var filterStrings = this.propertyFilterString.split(",");
301      var positive = [];
302      var negative = [];
303      filterStrings.forEach((f) => {
304        if (f.startsWith("!")) {
305          var str = f.substring(1);
306          negative.push((s) => s.indexOf(str) === -1);
307        } else {
308          var str = f;
309          positive.push((s) => s.indexOf(str) !== -1);
310        }
311      });
312      var filter = (item) => {
313        var apply = (f) => f(item.name);
314        return (positive.length === 0 || positive.some(apply))
315            && (negative.length === 0 || negative.every(apply));
316      };
317      filter.includeChildren = true;
318      return filter;
319    },
320  },
321  watch: {
322    title() {
323      document.title = this.title;
324    }
325  },
326  components: {
327    'tree-view': TreeView,
328    'timeline': Timeline,
329    'rects': Rects,
330  }
331}
332</script>
333
334<style>
335#app {
336}
337
338.main-content {
339  padding: 8px;
340}
341
342.card-toolbar {
343  border-bottom: 1px solid rgba(0, 0, 0, .12);
344}
345
346.timeline-card {
347  margin: 8px;
348}
349
350.timeline {
351  margin: 16px;
352}
353
354.screen {
355  border: 1px solid black;
356}
357
358.container {
359  display: flex;
360  flex-wrap: wrap;
361}
362
363.rects {
364  flex: none;
365  margin: 8px;
366}
367
368.hierarchy, .properties {
369  flex: 1;
370  margin: 8px;
371  min-width: 400px;
372}
373
374.hierarchy > .tree-view, .properties > .tree-view {
375  margin: 16px;
376}
377
378h1, h2 {
379  font-weight: normal;
380}
381
382ul {
383  list-style-type: none;
384  padding: 0;
385}
386
387li {
388  display: inline-block;
389  margin: 0 10px;
390}
391
392a {
393  color: #42b983;
394}
395</style>
396