Merge "Refactor file decoding, implement video view"

This commit is contained in:
Adam Pardyl
2019-07-23 07:48:43 +00:00
committed by Android (Google) Code Review
9 changed files with 595 additions and 376 deletions

View File

@@ -85,11 +85,11 @@ export default {
},
onTimelineItemSelected(index, timelineIndex) {
this.files[timelineIndex].selectedIndex = index;
var t = parseInt(this.files[timelineIndex].timeline[index].timestamp);
var t = parseInt(this.files[timelineIndex].timeline[index]);
for (var i = 0; i < this.files.length; i++) {
if (i != timelineIndex) {
this.files[i].selectedIndex = findLastMatchingSorted(this.files[i].timeline, function(array, idx) {
return parseInt(array[idx].timestamp) <= t;
return parseInt(array[idx]) <= t;
});
}
}
@@ -104,7 +104,7 @@ export default {
if (cur + direction < 0 || cur + direction >= this.files[idx].timeline.length) {
continue;
}
var d = Math.abs(parseInt(file.timeline[cur + direction].timestamp) - this.currentTimestamp);
var d = Math.abs(parseInt(file.timeline[cur + direction]) - this.currentTimestamp);
if (timeDiff > d) {
timeDiff = d;
closestTimeline = idx;
@@ -112,7 +112,7 @@ export default {
}
if (closestTimeline >= 0) {
this.files[closestTimeline].selectedIndex += direction;
this.currentTimestamp = parseInt(this.files[closestTimeline].timeline[this.files[closestTimeline].selectedIndex].timestamp);
this.currentTimestamp = parseInt(this.files[closestTimeline].timeline[this.files[closestTimeline].selectedIndex]);
}
},
onDataViewFocus(view) {
@@ -149,8 +149,8 @@ export default {
prettyDump: function() { return JSON.stringify(this.dump, null, 2); },
dataLoaded: function() { return this.files.length > 0 },
scale() {
var mx = Math.max(...(this.files.map(f => Math.max(...f.timeline.map(t => t.timestamp)))));
var mi = Math.min(...(this.files.map(f => Math.min(...f.timeline.map(t => t.timestamp)))));
var mx = Math.max(...(this.files.map(f => Math.max(...f.timeline))));
var mi = Math.min(...(this.files.map(f => Math.min(...f.timeline))));
return [mi, mx];
},
activeView: function() {

View File

@@ -55,23 +55,7 @@
</md-layout>
</template>
<script>
import jsonProtoDefs from 'frameworks/base/core/proto/android/server/windowmanagertrace.proto'
import jsonProtoDefsSF from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto'
import protobuf from 'protobufjs'
import { detectFile, dataFile, FILE_TYPES, DATA_TYPES } from './detectfile.js'
import { fill_transform_data } from './matrix_utils.js'
var protoDefs = protobuf.Root.fromJSON(jsonProtoDefs)
.addJSON(jsonProtoDefsSF.nested);
var TraceMessage = protoDefs.lookupType(
"com.android.server.wm.WindowManagerTraceFileProto");
var ServiceMessage = protoDefs.lookupType(
"com.android.server.wm.WindowManagerServiceDumpProto");
var LayersMessage = protoDefs.lookupType("android.surfaceflinger.LayersProto");
var LayersTraceMessage = protoDefs.lookupType("android.surfaceflinger.LayersTraceFileProto");
import { detectAndDecode, dataFile, FILE_TYPES, DATA_TYPES } from './decode.js'
export default {
name: 'datainput',
@@ -92,7 +76,7 @@ export default {
// No file selected.
return;
}
this.$emit('statusChange', this.filename + " (loading)");
this.$emit('statusChange', file.name + " (loading)");
var reader = new FileReader();
reader.onload = (e) => {
@@ -100,13 +84,9 @@ export default {
try {
if (FILE_TYPES[type]) {
var filetype = FILE_TYPES[type];
var decoded = filetype.protoType.decode(buffer);
modifyProtoFields(decoded, this.store.displayDefaults);
var transformed = filetype.transform(decoded);
var data = fileType.decoder(buffer, filetype, file.name, this.store);
} else {
var [filetype, decoded] = detectFile(buffer);
modifyProtoFields(decoded, this.store.displayDefaults);
var transformed = filetype.transform(decoded);
var [filetype, data] = detectAndDecode(buffer, file.name, this.store);
}
} catch (ex) {
this.$emit('statusChange', this.filename + ': ' + ex);
@@ -114,51 +94,8 @@ export default {
} finally {
event.target.value = ''
}
this.$emit('statusChange', this.filename + " (loading " + filetype.name + ")");
// Replace enum values with string representation and
// add default values to the proto objects. This function also handles
// a special case with TransformProtos where the matrix may be derived
// from the transform type.
function modifyProtoFields(protoObj, displayDefaults) {
if (!protoObj || protoObj !== Object(protoObj) || !protoObj.$type) {
return;
}
for (var fieldName in protoObj.$type.fields) {
var fieldProperties = protoObj.$type.fields[fieldName];
var field = protoObj[fieldName];
if (Array.isArray(field)) {
field.forEach((item, _) => {
modifyProtoFields(item, displayDefaults);
})
continue;
}
if (displayDefaults && !(field)) {
protoObj[fieldName] = fieldProperties.defaultValue;
}
if (fieldProperties.type === 'TransformProto') {
fill_transform_data(protoObj[fieldName]);
continue;
}
if (fieldProperties.resolvedType && fieldProperties.resolvedType.valuesById) {
protoObj[fieldName] = fieldProperties.resolvedType.valuesById[protoObj[fieldProperties.name]];
continue;
}
modifyProtoFields(protoObj[fieldName], displayDefaults);
}
}
var timeline;
if (filetype.timeline) {
timeline = transformed.children;
} else {
timeline = [transformed];
}
this.$set(this.dataFiles, filetype.dataType.name, dataFile(file.name, timeline, filetype.dataType));
this.$set(this.dataFiles, filetype.dataType.name, data);
this.$emit('statusChange', null);
}
reader.readAsArrayBuffer(files[0]);

View File

@@ -13,203 +13,47 @@
limitations under the License.
-->
<template>
<md-card v-if="tree">
<md-card v-if="file">
<md-card-header>
<div class="md-title">
<md-icon>{{file.type.icon}}</md-icon> {{file.filename}}
</div>
</md-card-header>
<md-card-content class="container">
<md-card class="rects">
<md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense">
<h2 class="md-title">Screen</h2>
</md-whiteframe>
<md-whiteframe md-elevation="8">
<rects :bounds="bounds" :rects="rects" :highlight="highlight" @rect-click="onRectClick" />
</md-whiteframe>
</md-card>
<md-card class="hierarchy">
<md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense">
<h2 class="md-title" style="flex: 1;">Hierarchy</h2>
<md-checkbox v-model="store.onlyVisible">Only visible</md-checkbox>
<md-checkbox v-model="store.flattened">Flat</md-checkbox>
</md-whiteframe>
<tree-view :item="tree" @item-selected="itemSelected" :selected="hierarchySelected" :filter="hierarchyFilter" :flattened="store.flattened" ref="hierarchy" />
</md-card>
<md-card class="properties">
<md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense">
<h2 class="md-title" style="flex: 1">Properties</h2>
<div class="filter">
<input id="filter" type="search" placeholder="Filter..." v-model="propertyFilterString" />
</div>
</md-whiteframe>
<tree-view :item="selectedTree" :filter="propertyFilter" />
</md-card>
</md-card-content>
<traceview v-if="isTrace" :store="store" :file="file" ref="view" />
<videoview v-if="isVideo" :file="file" ref="view" />
</md-card>
</template>
<script>
import TreeView from './TreeView.vue'
import Timeline from './Timeline.vue'
import Rects from './Rects.vue'
import { transform_json } from './transform.js'
import { format_transform_type, is_simple_transform } from './matrix_utils.js'
function formatProto(obj) {
if (!obj || !obj.$type) {
return;
}
if (obj.$type.fullName === '.android.surfaceflinger.RectProto' ||
obj.$type.fullName === '.android.graphics.RectProto') {
return `(${obj.left}, ${obj.top}) - (${obj.right}, ${obj.bottom})`;
} else if (obj.$type.fullName === '.android.surfaceflinger.FloatRectProto') {
return `(${obj.left.toFixed(3)}, ${obj.top.toFixed(3)}) - (${obj.right.toFixed(3)}, ${obj.bottom.toFixed(3)})`;
} else if (obj.$type.fullName === '.android.surfaceflinger.PositionProto') {
return `(${obj.x.toFixed(3)}, ${obj.y.toFixed(3)})`;
} else if (obj.$type.fullName === '.android.surfaceflinger.SizeProto') {
return `${obj.w} x ${obj.h}`;
} else if (obj.$type.fullName === '.android.surfaceflinger.ColorProto') {
return `r:${obj.r} g:${obj.g} \n b:${obj.b} a:${obj.a}`;
} else if (obj.$type.fullName === '.android.surfaceflinger.TransformProto') {
var transform_type = format_transform_type(obj);
if (is_simple_transform(obj)) {
return `${transform_type}`;
}
return `${transform_type} dsdx:${obj.dsdx.toFixed(3)} dtdx:${obj.dtdx.toFixed(3)} dsdy:${obj.dsdy.toFixed(3)} dtdy:${obj.dtdy.toFixed(3)}`;
}
}
import TraceView from './TraceView.vue'
import VideoView from './VideoView.vue'
import { DATA_TYPES } from './decode.js'
export default {
name: 'dataview',
data() {
return {
propertyFilterString: "",
selectedTree: {},
hierarchySelected: null,
lastSelectedStableId: null,
bounds: {},
rects: [],
tree: null,
highlight: null,
}
return {}
},
methods: {
itemSelected(item) {
this.hierarchySelected = item;
this.selectedTree = transform_json(item.obj, item.name, {
skip: item.skip,
formatter: formatProto
});
this.highlight = item.highlight;
this.lastSelectedStableId = item.stableId;
this.$emit('focus');
},
onRectClick(item) {
if (item) {
this.itemSelected(item);
}
},
setData(item) {
this.tree = item;
this.rects = [...item.rects].reverse();
this.bounds = item.bounds;
this.hierarchySelected = null;
this.selectedTree = {};
this.highlight = null;
function find_item(item, stableId) {
if (item.stableId === stableId) {
return item;
}
if (Array.isArray(item.children)) {
for (var child of item.children) {
var found = find_item(child, stableId);
if (found) {
return found;
}
}
}
return null;
}
if (this.lastSelectedStableId) {
var found = find_item(item, this.lastSelectedStableId);
if (found) {
this.itemSelected(found);
}
}
},
arrowUp() {
return this.$refs.hierarchy.selectPrev();
return this.$refs.view.arrowUp();
},
arrowDown() {
return this.$refs.hierarchy.selectNext();
return this.$refs.view.arrowDown();
},
},
created() {
this.setData(this.file.timeline[this.file.selectedIndex]);
},
watch: {
selectedIndex() {
this.setData(this.file.timeline[this.file.selectedIndex]);
}
},
props: ['store', 'file'],
computed: {
selectedIndex() {
return this.file.selectedIndex;
isTrace() {
return this.file.type == DATA_TYPES.WINDOW_MANAGER || this.file.type == DATA_TYPES.SURFACE_FLINGER;
},
hierarchyFilter() {
return this.store.onlyVisible ? (c, flattened) => {
return c.visible || c.childrenVisible && !flattened;
} : null;
},
propertyFilter() {
var filterStrings = this.propertyFilterString.split(",");
var positive = [];
var negative = [];
filterStrings.forEach((f) => {
if (f.startsWith("!")) {
var str = f.substring(1);
negative.push((s) => s.indexOf(str) === -1);
} else {
var str = f;
positive.push((s) => s.indexOf(str) !== -1);
}
});
var filter = (item) => {
var apply = (f) => f(item.name);
return (positive.length === 0 || positive.some(apply)) &&
(negative.length === 0 || negative.every(apply));
};
filter.includeChildren = true;
return filter;
isVideo() {
return this.file.type == DATA_TYPES.SCREEN_RECORDING;
}
},
components: {
'tree-view': TreeView,
'rects': Rects,
'traceview': TraceView,
'videoview': VideoView,
}
}
</script>
<style>
.rects {
flex: none;
margin: 8px;
}
.hierarchy,
.properties {
flex: 1;
margin: 8px;
min-width: 400px;
}
.hierarchy>.tree-view,
.properties>.tree-view {
margin: 16px;
}
</style>

View File

@@ -27,7 +27,7 @@ export default {
},
methods: {
position(item) {
return this.translate(item.timestamp);
return this.translate(item);
},
translate(cx) {
var scale = [...this.scale];
@@ -45,7 +45,7 @@ export default {
if (this.items.length == 1) {
return [0];
}
return this.items.map((e) => parseInt(e.timestamp));
return this.items;
},
selected() {
return this.items[this.selectedIndex];

View File

@@ -0,0 +1,209 @@
<!-- Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<template>
<md-card-content class="container">
<md-card class="rects">
<md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense">
<h2 class="md-title">Screen</h2>
</md-whiteframe>
<md-whiteframe md-elevation="8">
<rects :bounds="bounds" :rects="rects" :highlight="highlight" @rect-click="onRectClick" />
</md-whiteframe>
</md-card>
<md-card class="hierarchy">
<md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense">
<h2 class="md-title" style="flex: 1;">Hierarchy</h2>
<md-checkbox v-model="store.onlyVisible">Only visible</md-checkbox>
<md-checkbox v-model="store.flattened">Flat</md-checkbox>
</md-whiteframe>
<tree-view :item="tree" @item-selected="itemSelected" :selected="hierarchySelected" :filter="hierarchyFilter" :flattened="store.flattened" ref="hierarchy" />
</md-card>
<md-card class="properties">
<md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense">
<h2 class="md-title" style="flex: 1">Properties</h2>
<div class="filter">
<input id="filter" type="search" placeholder="Filter..." v-model="propertyFilterString" />
</div>
</md-whiteframe>
<tree-view :item="selectedTree" :filter="propertyFilter" />
</md-card>
</md-card-content>
</template>
<script>
import TreeView from './TreeView.vue'
import Timeline from './Timeline.vue'
import Rects from './Rects.vue'
import { transform_json } from './transform.js'
import { format_transform_type, is_simple_transform } from './matrix_utils.js'
function formatProto(obj) {
if (!obj || !obj.$type) {
return;
}
if (obj.$type.fullName === '.android.surfaceflinger.RectProto' ||
obj.$type.fullName === '.android.graphics.RectProto') {
return `(${obj.left}, ${obj.top}) - (${obj.right}, ${obj.bottom})`;
} else if (obj.$type.fullName === '.android.surfaceflinger.FloatRectProto') {
return `(${obj.left.toFixed(3)}, ${obj.top.toFixed(3)}) - (${obj.right.toFixed(3)}, ${obj.bottom.toFixed(3)})`;
} else if (obj.$type.fullName === '.android.surfaceflinger.PositionProto') {
return `(${obj.x.toFixed(3)}, ${obj.y.toFixed(3)})`;
} else if (obj.$type.fullName === '.android.surfaceflinger.SizeProto') {
return `${obj.w} x ${obj.h}`;
} else if (obj.$type.fullName === '.android.surfaceflinger.ColorProto') {
return `r:${obj.r} g:${obj.g} \n b:${obj.b} a:${obj.a}`;
} else if (obj.$type.fullName === '.android.surfaceflinger.TransformProto') {
var transform_type = format_transform_type(obj);
if (is_simple_transform(obj)) {
return `${transform_type}`;
}
return `${transform_type} dsdx:${obj.dsdx.toFixed(3)} dtdx:${obj.dtdx.toFixed(3)} dsdy:${obj.dsdy.toFixed(3)} dtdy:${obj.dtdy.toFixed(3)}`;
}
}
export default {
name: 'traceview',
data() {
return {
propertyFilterString: "",
selectedTree: {},
hierarchySelected: null,
lastSelectedStableId: null,
bounds: {},
rects: [],
tree: null,
highlight: null,
}
},
methods: {
itemSelected(item) {
this.hierarchySelected = item;
this.selectedTree = transform_json(item.obj, item.name, {
skip: item.skip,
formatter: formatProto
});
this.highlight = item.highlight;
this.lastSelectedStableId = item.stableId;
this.$emit('focus');
},
onRectClick(item) {
if (item) {
this.itemSelected(item);
}
},
setData(item) {
this.tree = item;
this.rects = [...item.rects].reverse();
this.bounds = item.bounds;
this.hierarchySelected = null;
this.selectedTree = {};
this.highlight = null;
function find_item(item, stableId) {
if (item.stableId === stableId) {
return item;
}
if (Array.isArray(item.children)) {
for (var child of item.children) {
var found = find_item(child, stableId);
if (found) {
return found;
}
}
}
return null;
}
if (this.lastSelectedStableId) {
var found = find_item(item, this.lastSelectedStableId);
if (found) {
this.itemSelected(found);
}
}
},
arrowUp() {
return this.$refs.hierarchy.selectPrev();
},
arrowDown() {
return this.$refs.hierarchy.selectNext();
},
},
created() {
this.setData(this.file.data[this.file.selectedIndex]);
},
watch: {
selectedIndex() {
this.setData(this.file.data[this.file.selectedIndex]);
}
},
props: ['store', 'file'],
computed: {
selectedIndex() {
return this.file.selectedIndex;
},
hierarchyFilter() {
return this.store.onlyVisible ? (c, flattened) => {
return c.visible || c.childrenVisible && !flattened;
} : null;
},
propertyFilter() {
var filterStrings = this.propertyFilterString.split(",");
var positive = [];
var negative = [];
filterStrings.forEach((f) => {
if (f.startsWith("!")) {
var str = f.substring(1);
negative.push((s) => s.indexOf(str) === -1);
} else {
var str = f;
positive.push((s) => s.indexOf(str) !== -1);
}
});
var filter = (item) => {
var apply = (f) => f(item.name);
return (positive.length === 0 || positive.some(apply)) &&
(negative.length === 0 || negative.every(apply));
};
filter.includeChildren = true;
return filter;
}
},
components: {
'tree-view': TreeView,
'rects': Rects,
}
}
</script>
<style>
.rects {
flex: none;
margin: 8px;
}
.hierarchy,
.properties {
flex: 1;
margin: 8px;
min-width: 400px;
}
.hierarchy>.tree-view,
.properties>.tree-view {
margin: 16px;
}
</style>

View File

@@ -0,0 +1,78 @@
<!-- Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<template>
<md-card-content class="container">
<md-card class="rects">
<md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense">
<h2 class="md-title">Screen</h2>
</md-whiteframe>
<md-whiteframe md-elevation="8">
<video :id="file.filename" class="screen" :src="videoUri" />
</md-whiteframe>
</md-card>
</md-card-content>
</template>
<script>
const EPSILON = 0.00001
function uint8ToString(array) {
var chunk = 0x8000;
var out = [];
for (var i = 0; i < array.length; i += chunk) {
out.push(String.fromCharCode.apply(null, array.subarray(i, i + chunk)));
}
return out.join("");
}
export default {
name: 'videoview',
data() {
return {}
},
methods: {
arrowUp() {
return true
},
arrowDown() {
return true;
},
selectFrame(idx) {
var time = (this.file.timeline[idx] - this.file.timeline[0]) / 1000000000 + EPSILON;
document.getElementById(this.file.filename).currentTime = time;
},
},
watch: {
selectedIndex() {
this.selectFrame(this.file.selectedIndex);
}
},
props: ['file'],
computed: {
selectedIndex() {
return this.file.selectedIndex;
},
videoUri() {
return "data:video/mp4;base64," + btoa(uint8ToString(this.file.data));
}
},
}
</script>
<style>
.screen {
max-height: 50em;
}
</style>

View File

@@ -0,0 +1,211 @@
/*
* Copyright 2017, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import jsonProtoDefs from 'frameworks/base/core/proto/android/server/windowmanagertrace.proto'
import jsonProtoDefsSF from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto'
import protobuf from 'protobufjs'
import { transform_layers, transform_layers_trace } from './transform_sf.js'
import { transform_window_service, transform_window_trace } from './transform_wm.js'
import { fill_transform_data } from './matrix_utils.js'
import { mp4Decoder } from './decodeVideo.js'
var protoDefs = protobuf.Root.fromJSON(jsonProtoDefs)
.addJSON(jsonProtoDefsSF.nested);
var WindowTraceMessage = protoDefs.lookupType(
"com.android.server.wm.WindowManagerTraceFileProto");
var WindowMessage = protoDefs.lookupType(
"com.android.server.wm.WindowManagerServiceDumpProto");
var LayersMessage = protoDefs.lookupType("android.surfaceflinger.LayersProto");
var LayersTraceMessage = protoDefs.lookupType("android.surfaceflinger.LayersTraceFileProto");
const LAYER_TRACE_MAGIC_NUMBER = [0x09, 0x4c, 0x59, 0x52, 0x54, 0x52, 0x41, 0x43, 0x45] // .LYRTRACE
const WINDOW_TRACE_MAGIC_NUMBER = [0x09, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45] // .WINTRACE
const MPEG4_MAGIC_NMBER = [0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32] // ....ftypmp42
const DATA_TYPES = {
WINDOW_MANAGER: {
name: "WindowManager",
icon: "view_compact",
},
SURFACE_FLINGER: {
name: "SurfaceFlinger",
icon: "filter_none",
},
SCREEN_RECORDING: {
name: "Screen recording",
icon: "videocam",
}
}
const FILE_TYPES = {
'window_trace': {
name: "WindowManager trace",
dataType: DATA_TYPES.WINDOW_MANAGER,
decoder: protoDecoder,
decoderParams: {
protoType: WindowTraceMessage,
transform: transform_window_trace,
timeline: true,
},
},
'layers_trace': {
name: "SurfaceFlinger trace",
dataType: DATA_TYPES.SURFACE_FLINGER,
decoder: protoDecoder,
decoderParams: {
protoType: LayersTraceMessage,
transform: transform_layers_trace,
timeline: true,
},
},
'layers_dump': {
name: "SurfaceFlinger dump",
dataType: DATA_TYPES.SURFACE_FLINGER,
decoder: protoDecoder,
decoderParams: {
protoType: LayersMessage,
transform: transform_layers,
timeline: false,
},
},
'window_dump': {
name: "WindowManager dump",
dataType: DATA_TYPES.WINDOW_MANAGER,
decoder: protoDecoder,
decoderParams: {
protoType: WindowMessage,
transform: transform_window_service,
timeline: false,
},
},
'screen_recording': {
name: "Screen recording",
dataType: DATA_TYPES.SCREEN_RECORDING,
decoder: videoDecoder,
decoderParams: {
videoDecoder: mp4Decoder,
},
}
};
// Replace enum values with string representation and
// add default values to the proto objects. This function also handles
// a special case with TransformProtos where the matrix may be derived
// from the transform type.
function modifyProtoFields(protoObj, displayDefaults) {
if (!protoObj || protoObj !== Object(protoObj) || !protoObj.$type) {
return;
}
for (var fieldName in protoObj.$type.fields) {
var fieldProperties = protoObj.$type.fields[fieldName];
var field = protoObj[fieldName];
if (Array.isArray(field)) {
field.forEach((item, _) => {
modifyProtoFields(item, displayDefaults);
})
continue;
}
if (displayDefaults && !(field)) {
protoObj[fieldName] = fieldProperties.defaultValue;
}
if (fieldProperties.type === 'TransformProto') {
fill_transform_data(protoObj[fieldName]);
continue;
}
if (fieldProperties.resolvedType && fieldProperties.resolvedType.valuesById) {
protoObj[fieldName] = fieldProperties.resolvedType.valuesById[protoObj[fieldProperties.name]];
continue;
}
modifyProtoFields(protoObj[fieldName], displayDefaults);
}
}
function protoDecoder(buffer, fileType, fileName, store) {
var decoded = fileType.decoderParams.protoType.decode(buffer);
modifyProtoFields(decoded, store.displayDefaults);
var transformed = fileType.decoderParams.transform(decoded);
var data
if (fileType.decoderParams.timeline) {
data = transformed.children;
} else {
data = [transformed];
}
return dataFile(fileName, data.map(x => x.timestamp), data, fileType.dataType);
}
function videoDecoder(buffer, fileType, fileName, store) {
var [data, timeline] = fileType.decoderParams.videoDecoder(buffer)
return dataFile(fileName, timeline, data, fileType.dataType);
}
function dataFile(filename, timeline, data, type) {
return {
filename: filename,
timeline: timeline,
data: data,
type: type,
selectedIndex: 0,
}
}
function arrayEquals(a, b) {
if (a.length !== b.length) {
return false;
}
for (var i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}
function arrayStartsWith(array, prefix) {
return arrayEquals(array.slice(0, prefix.length), prefix);
}
function decodedFile(fileType, buffer, fileName, store) {
return [fileType, fileType.decoder(buffer, fileType, fileName, store)];
}
function detectAndDecode(buffer, fileName, store) {
if (arrayStartsWith(buffer, LAYER_TRACE_MAGIC_NUMBER)) {
return decodedFile(FILE_TYPES['layers_trace'], buffer, fileName, store);
}
if (arrayStartsWith(buffer, WINDOW_TRACE_MAGIC_NUMBER)) {
return decodedFile(FILE_TYPES['window_trace'], buffer, fileName, store);
}
if (arrayStartsWith(buffer, MPEG4_MAGIC_NMBER)) {
return decodedFile(FILE_TYPES['screen_recording'], buffer, fileName, store);
}
for (var name of ['layers_dump', 'window_dump']) {
try {
return decodedFile(FILE_TYPES[name], buffer, fileName, store);
} catch (ex) {
// ignore exception and try next filetype
}
}
throw new Error('Unable to detect file');
}
export { detectAndDecode, dataFile, DATA_TYPES, FILE_TYPES };

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2017, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const WINSCOPE_META_MAGIC_STRING = [0x23, 0x56, 0x56, 0x31, 0x4e, 0x53, 0x43, 0x30, 0x50, 0x45, 0x54, 0x31, 0x4d, 0x45, 0x21, 0x23]; // #VV1NSC0PET1ME!#
// Suitable only for short patterns
function findFirstInArray(array, pattern) {
for (var i = 0; i < array.length; i++) {
var match = true;
for (var j = 0; j < pattern.length; j++) {
if (array[i + j] != pattern[j]) {
match = false;
break;
}
}
if (match) {
return i;
}
}
return -1;
}
function parseUintNLE(buffer, position, bytes) {
var num = 0;
for (var i = bytes - 1; i >= 0; i--) {
num = num * 256
num += buffer[position + i];
}
return num;
}
function parseUint32LE(buffer, position) {
return parseUintNLE(buffer, position, 4)
}
function parseUint64LE(buffer, position) {
return parseUintNLE(buffer, position, 8)
}
function mp4Decoder(buffer) {
var dataStart = findFirstInArray(buffer, WINSCOPE_META_MAGIC_STRING);
if (dataStart < 0) {
throw new Error('Unable to find sync metadata in the file');
}
dataStart += WINSCOPE_META_MAGIC_STRING.length;
var frameNum = parseUint32LE(buffer, dataStart);
dataStart += 4;
var timeline = [];
for (var i = 0; i < frameNum; i++) {
timeline.push(parseUint64LE(buffer, dataStart) * 1000);
dataStart += 8;
}
return [buffer, timeline]
}
export { mp4Decoder };

View File

@@ -1,129 +0,0 @@
/*
* Copyright 2017, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import jsonProtoDefs from 'frameworks/base/core/proto/android/server/windowmanagertrace.proto'
import jsonProtoDefsSF from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto'
import protobuf from 'protobufjs'
import { transform_layers, transform_layers_trace } from './transform_sf.js'
import { transform_window_service, transform_window_trace } from './transform_wm.js'
var protoDefs = protobuf.Root.fromJSON(jsonProtoDefs)
.addJSON(jsonProtoDefsSF.nested);
var WindowTraceMessage = protoDefs.lookupType(
"com.android.server.wm.WindowManagerTraceFileProto");
var WindowMessage = protoDefs.lookupType(
"com.android.server.wm.WindowManagerServiceDumpProto");
var LayersMessage = protoDefs.lookupType("android.surfaceflinger.LayersProto");
var LayersTraceMessage = protoDefs.lookupType("android.surfaceflinger.LayersTraceFileProto");
const LAYER_TRACE_MAGIC_NUMBER = [0x09, 0x4c, 0x59, 0x52, 0x54, 0x52, 0x41, 0x43, 0x45] // .LYRTRACE
const WINDOW_TRACE_MAGIC_NUMBER = [0x09, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45] // .WINTRACE
const DATA_TYPES = {
WINDOW_MANAGER: {
name: "WindowManager",
icon: "view_compact",
},
SURFACE_FLINGER: {
name: "SurfaceFlinger",
icon: "filter_none",
},
}
const FILE_TYPES = {
'window_trace': {
protoType: WindowTraceMessage,
transform: transform_window_trace,
name: "WindowManager trace",
timeline: true,
dataType: DATA_TYPES.WINDOW_MANAGER,
},
'layers_trace': {
protoType: LayersTraceMessage,
transform: transform_layers_trace,
name: "SurfaceFlinger trace",
timeline: true,
dataType: DATA_TYPES.SURFACE_FLINGER,
},
'layers_dump': {
protoType: LayersMessage,
transform: transform_layers,
name: "SurfaceFlinger dump",
timeline: false,
dataType: DATA_TYPES.SURFACE_FLINGER,
},
'window_dump': {
protoType: WindowMessage,
transform: transform_window_service,
name: "WindowManager dump",
timeline: false,
dataType: DATA_TYPES.WINDOW_MANAGER,
},
};
function dataFile(filename, timeline, type) {
return {
filename: filename,
timeline: timeline,
type: type,
selectedIndex: 0,
}
}
function arrayEquals(a, b) {
if (a.length !== b.length) {
return false;
}
for (var i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}
function arrayStartsWith(array, prefix) {
return arrayEquals(array.slice(0, prefix.length), prefix);
}
function decodedFile(filename, buffer) {
var decoded = FILE_TYPES[filename].protoType.decode(buffer);
return [FILE_TYPES[filename], decoded];
}
function detectFile(buffer) {
if (arrayStartsWith(buffer, LAYER_TRACE_MAGIC_NUMBER)) {
return decodedFile('layers_trace', buffer);
}
if (arrayStartsWith(buffer, WINDOW_TRACE_MAGIC_NUMBER)) {
return decodedFile('window_trace', buffer);
}
for (var filename of ['layers_dump', 'window_dump']) {
try {
var [filetype, decoded] = decodedFile(filename, buffer);
var transformed = filetype.transform(decoded);
return [FILE_TYPES[filename], decoded];
} catch (ex) {
// ignore exception and try next filetype
}
}
throw new Error('Unable to detect file');
}
export { detectFile, dataFile, DATA_TYPES, FILE_TYPES };