Merge changes I85f52bd3,Ia91262e4,Ie084d288,I693815ff,If696d330, ...

* changes:
  WinScope: Support missing layers
  Winscope: Fix how layers with transforms and positions are rendered
  WinScope: Modify instead of replacing proto objects
  Support SF traces without any layers (needed when booting SF)
  Winscope: Translate layer flags in Surface flinger dump
  Update `Display default` checkbox label
  Add Type to transform objects.
  Fix visibility rule for SF traces on Winscope
  Allow SF traces to omit default values on the property list
  Fix "TypeError: Cannot read property 'x' of null" on SF traces
  Display only elements actually visible on the screen as "Visible"
  Display SF layer based on size, crop & parent instead of buffer size
  WinScope: Support boundless surfaces in WinScope
  Winscope: Use a unique stableId for all layers and wm entries
  Winscope: keep backwards compatibility prior to windowFrames
This commit is contained in:
Treehugger Robot
2019-07-02 13:58:25 +00:00
committed by Gerrit Code Review
6 changed files with 393 additions and 36 deletions

View File

@@ -17,6 +17,15 @@
<md-whiteframe md-tag="md-toolbar"> <md-whiteframe md-tag="md-toolbar">
<h1 class="md-title" style="flex: 1">{{title}}</h1> <h1 class="md-title" style="flex: 1">{{title}}</h1>
<div>
<md-checkbox v-model="store.displayDefaults">Show default properties
<md-tooltip md-direction="bottom">
If checked, shows the value of all properties.
Otherwise, hides all properties whose value is the default for its data type.
</md-tooltip>
</md-checkbox>
</div>
<input type="file" @change="onLoadFile" id="upload-file" v-show="false"/> <input type="file" @change="onLoadFile" id="upload-file" v-show="false"/>
<label class="md-button md-accent md-raised md-theme-default" for="upload-file">Open File</label> <label class="md-button md-accent md-raised md-theme-default" for="upload-file">Open File</label>
@@ -82,6 +91,7 @@ import LocalStore from './localstore.js'
import {transform_json} from './transform.js' import {transform_json} from './transform.js'
import {transform_layers, transform_layers_trace} from './transform_sf.js' import {transform_layers, transform_layers_trace} from './transform_sf.js'
import {transform_window_service, transform_window_trace} from './transform_wm.js' import {transform_window_service, transform_window_trace} from './transform_wm.js'
import {fill_transform_data, format_transform_type, is_simple_transform} from './matrix_utils.js'
var protoDefs = protobuf.Root.fromJSON(jsonProtoDefs) var protoDefs = protobuf.Root.fromJSON(jsonProtoDefs)
@@ -98,14 +108,24 @@ function formatProto(obj) {
if (!obj || !obj.$type) { if (!obj || !obj.$type) {
return; return;
} }
if (obj.$type.fullName === '.android.surfaceflinger.RectProto') { if (obj.$type.fullName === '.android.surfaceflinger.RectProto' ||
obj.$type.fullName === '.android.graphics.RectProto') {
return `(${obj.left}, ${obj.top}) - (${obj.right}, ${obj.bottom})`; return `(${obj.left}, ${obj.top}) - (${obj.right}, ${obj.bottom})`;
} else if (obj.$type.fullName === '.android.surfaceflinger.PositionProto') { } else if (obj.$type.fullName === '.android.surfaceflinger.FloatRectProto') {
return `(${obj.x},${obj.y})`; 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') { } else if (obj.$type.fullName === '.android.surfaceflinger.SizeProto') {
return `${obj.w} x ${obj.h}`; return `${obj.w} x ${obj.h}`;
} else if (obj.$type.fullName === '.android.surfaceflinger.ColorProto') { } else if (obj.$type.fullName === '.android.surfaceflinger.ColorProto') {
return `r:${obj.r} g:${obj.g} b:${obj.b} a:${obj.a}`; 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)}`;
} }
} }
@@ -155,6 +175,7 @@ export default {
store: LocalStore('app', { store: LocalStore('app', {
flattened: false, flattened: false,
onlyVisible: false, onlyVisible: false,
displayDefaults: true
}), }),
FILE_TYPES, FILE_TYPES,
fileType: "auto", fileType: "auto",
@@ -189,9 +210,46 @@ export default {
} }
this.title = this.filename + " (loading " + filetype.name + ")"; this.title = 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);
}
}
try { try {
var decoded = filetype.protoType.decode(buffer); var decoded = filetype.protoType.decode(buffer);
decoded = filetype.protoType.toObject(decoded, {enums: String, defaults: true}); modifyProtoFields(decoded, this.store.displayDefaults);
var transformed = filetype.transform(decoded); var transformed = filetype.transform(decoded);
} catch (ex) { } catch (ex) {
this.title = this.filename + " (loading " + filetype.name + "):" + ex; this.title = this.filename + " (loading " + filetype.name + "):" + ex;

View File

@@ -65,7 +65,7 @@ export default {
var w = this.s(r.right) - this.s(r.left); var w = this.s(r.right) - this.s(r.left);
var h = this.s(r.bottom) - this.s(r.top); var h = this.s(r.bottom) - this.s(r.top);
var t = r.transform; var t = r.transform;
var tr = t ? `matrix(${t.dsdx}, ${t.dtdx}, ${t.dsdy}, ${t.dtdy}, 0, 0)` : ''; var tr = t ? `matrix(${t.dsdx}, ${t.dtdx}, ${t.dsdy}, ${t.dtdy}, ${this.s(t.tx)}, ${this.s(t.ty)})` : '';
return `top: ${y}px; left: ${x}px; height: ${h}px; width: ${w}px;` + return `top: ${y}px; left: ${x}px; height: ${h}px; width: ${w}px;` +
`transform: ${tr}; transform-origin: 0 0;` `transform: ${tr}; transform-origin: 0 0;`
}, },

View File

@@ -0,0 +1,175 @@
/*
* Copyright 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.
*/
/* transform type flags */
const TRANSLATE_VAL = 0x0001;
const ROTATE_VAL = 0x0002;
const SCALE_VAL = 0x0004;
/* orientation flags */
const FLIP_H_VAL = 0x0100; // (1 << 0 << 8)
const FLIP_V_VAL = 0x0200; // (1 << 1 << 8)
const ROT_90_VAL = 0x0400; // (1 << 2 << 8)
const ROT_INVALID_VAL = 0x8000; // (0x80 << 8)
function is_proto_2(transform) {
/*
* Checks if the loaded file was a stored with ProtoBuf2 or Protobuf3
*
* Proto2 files don't have a Type for the transform object but all other
* fields of the transform are set.
*
* Proto3 has a type field for the transform but doesn't store default
* values (0 for transform type), also, the framework/native implementation
* doesn't write a transform in case it is an identity matrix.
*/
var propertyNames = Object.getOwnPropertyNames(transform);
return (!propertyNames.includes("type") && propertyNames.includes("dsdx"));
}
function is_simple_transform(transform) {
transform = transform || {};
if (is_proto_2(transform)) {
return false;
}
return is_type_flag_clear(transform, ROT_INVALID_VAL|SCALE_VAL);
}
/**
* Converts a transform type into readable format.
* Adapted from the dump function from framework/native
*
* @param {*} transform Transform object ot be converter
*/
function format_transform_type(transform) {
if (is_proto_2(transform)) {
return "";
}
if (is_type_flag_clear(transform, SCALE_VAL | ROTATE_VAL | TRANSLATE_VAL)) {
return "IDENTITY";
}
var type_flags = [];
if (is_type_flag_set(transform, SCALE_VAL)) {
type_flags.push("SCALE");
}
if (is_type_flag_set(transform, TRANSLATE_VAL)) {
type_flags.push("TRANSLATE");
}
if (is_type_flag_set(transform, ROT_INVALID_VAL)) {
type_flags.push("ROT_INVALID");
} else if (is_type_flag_set(transform, ROT_90_VAL|FLIP_V_VAL|FLIP_H_VAL)) {
type_flags.push("ROT_270");
} else if (is_type_flag_set(transform, FLIP_V_VAL|FLIP_H_VAL)) {
type_flags.push("ROT_180");
} else {
if (is_type_flag_set(transform, ROT_90_VAL)) {
type_flags.push("ROT_90");
}
if (is_type_flag_set(transform, FLIP_V_VAL)) {
type_flags.push("FLIP_V");
}
if (is_type_flag_set(transform, FLIP_H_VAL)) {
type_flags.push("FLIP_H");
}
}
if (type_flags.length == 0) {
throw "Unknown transform type " + transform ;
}
return type_flags.join(', ');
}
/**
* Ensures all values of the transform object are set.
*/
function fill_transform_data(transform) {
function fill_simple_transform(transform) {
// ROT_270 = ROT_90|FLIP_H|FLIP_V;
if (is_type_flag_set(transform, ROT_90_VAL|FLIP_V_VAL|FLIP_H_VAL)) {
transform.dsdx = 0.0;
transform.dtdx = -1.0;
transform.dsdy = 1.0;
transform.dtdy = 0.0;
return;
}
// ROT_180 = FLIP_H|FLIP_V;
if (is_type_flag_set(transform, FLIP_V_VAL|FLIP_H_VAL)) {
transform.dsdx = -1.0;
transform.dtdx = 0.0;
transform.dsdy = 0.0;
transform.dtdy = -1.0;
return;
}
// ROT_90
if (is_type_flag_set(transform, ROT_90_VAL)) {
transform.dsdx = 0.0;
transform.dtdx = 1.0;
transform.dsdy = -1.0;
transform.dtdy = 0.0;
return;
}
// IDENTITY
if (is_type_flag_clear(transform, SCALE_VAL | ROTATE_VAL)) {
transform.dsdx = 1.0;
transform.dtdx = 0.0;
transform.dsdy = 0.0;
transform.dtdy = 1.0;
transform.type = 0;
return;
}
throw "Unknown transform type " + transform;
}
if (!transform) {
return;
}
if (is_proto_2(transform)) {
return;
}
if (is_simple_transform(transform)){
fill_simple_transform(transform);
}
transform.dsdx = transform.dsdx || 0.0;
transform.dtdx = transform.dtdx || 0.0;
transform.dsdy = transform.dsdy || 0.0;
transform.dtdy = transform.dtdy || 0.0;
}
function is_type_flag_set(transform, bits) {
transform = transform || {};
var type = transform.type || 0;
return (type & bits) === bits;
}
function is_type_flag_clear(transform, bits) {
transform = transform || {};
var type = transform.type || 0;
return (type & bits) === 0;
}
export {format_transform_type, fill_transform_data, is_simple_transform};

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
function transform({obj, kind, name, children, timestamp, rect, bounds, highlight, rects_transform, chips, visible, flattened}) { function transform({obj, kind, name, children, timestamp, rect, bounds, highlight, rects_transform, chips, visible, flattened, stableId}) {
function call(fn, arg) { function call(fn, arg) {
return (typeof fn == 'function') ? fn(arg) : fn; return (typeof fn == 'function') ? fn(arg) : fn;
} }
@@ -55,6 +55,9 @@ function transform({obj, kind, name, children, timestamp, rect, bounds, highligh
var kindResolved = call(kind, obj); var kindResolved = call(kind, obj);
var nameResolved = call(name, obj); var nameResolved = call(name, obj);
var rectResolved = call(rect, obj); var rectResolved = call(rect, obj);
var stableIdResolved = (stableId === undefined) ?
kindResolved + '|-|' + nameResolved :
call(stableId, obj);
var result = { var result = {
kind: kindResolved, kind: kindResolved,
@@ -68,7 +71,7 @@ function transform({obj, kind, name, children, timestamp, rect, bounds, highligh
rects: rects_transform(concat(rectResolved, transformed_children, (e) => e.rects)), rects: rects_transform(concat(rectResolved, transformed_children, (e) => e.rects)),
highlight: call(highlight, obj), highlight: call(highlight, obj),
chips: call(chips, obj), chips: call(chips, obj),
stableId: kindResolved + "|-|" + nameResolved, stableId: stableIdResolved,
visible: call(visible, obj), visible: call(visible, obj),
childrenVisible: transformed_children.some((c) => { childrenVisible: transformed_children.some((c) => {
return c.childrenVisible || c.visible return c.childrenVisible || c.visible

View File

@@ -16,7 +16,11 @@
import {transform, nanos_to_string, get_visible_chip} from './transform.js' import {transform, nanos_to_string, get_visible_chip} from './transform.js'
const FLAG_HIDDEN = 0x1; // Layer flags
const FLAG_HIDDEN = 0x01;
const FLAG_OPAQUE = 0x02;
const FLAG_SECURE = 0x80;
var RELATIVE_Z_CHIP = {short: 'RelZ', var RELATIVE_Z_CHIP = {short: 'RelZ',
long: "Is relative Z-ordered to another surface", long: "Is relative Z-ordered to another surface",
class: 'warn'}; class: 'warn'};
@@ -27,36 +31,139 @@ var MISSING_LAYER = {short: 'MissingLayer',
long: "This layer was referenced from the parent, but not present in the trace", long: "This layer was referenced from the parent, but not present in the trace",
class: 'error'}; class: 'error'};
function transform_layer(layer, {parentHidden}) { function transform_layer(layer, {parentBounds, parentHidden}) {
function transform_rect(layer) {
var pos = layer.position || {};
var size = layer.size || {};
function get_size(layer) {
var size = layer.size || {w: 0, h: 0};
return { return {
left: pos.x || 0, left: 0,
right: pos.x + size.w || 0, right: size.w,
top: pos.y || 0, top: 0,
bottom: pos.y + size.h || 0, bottom: size.h
label: layer.name, };
transform: layer.transform,
} }
function get_crop(layer) {
var crop = layer.crop || {left: 0, top: 0, right: 0 , bottom:0};
return {
left: crop.left || 0,
right: crop.right || 0,
top: crop.top || 0,
bottom: crop.bottom || 0
};
}
function intersect(bounds, crop) {
return {
left: Math.max(crop.left, bounds.left),
right: Math.min(crop.right, bounds.right),
top: Math.max(crop.top, bounds.top),
bottom: Math.min(crop.bottom, bounds.bottom),
};
}
function is_empty_rect(rect) {
var right = rect.right || 0;
var left = rect.left || 0;
var top = rect.top || 0;
var bottom = rect.bottom || 0;
return (right - left) <= 0 || (bottom - top) <= 0;
}
function get_cropped_bounds(layer, parentBounds) {
var size = get_size(layer);
var crop = get_crop(layer);
if (!is_empty_rect(size) && !is_empty_rect(crop)) {
return intersect(size, crop);
}
if (!is_empty_rect(size)) {
return size;
}
if (!is_empty_rect(crop)) {
return crop;
}
return parentBounds || { left: 0, right: 0, top: 0, bottom: 0 };
}
function offset_to(bounds, x, y) {
return {
right: bounds.right - (bounds.left - x),
bottom: bounds.bottom - (bounds.top - y),
left: x,
top: y,
};
}
function transform_bounds(layer, parentBounds) {
var result = layer.bounds || get_cropped_bounds(layer, parentBounds);
var tx = (layer.position) ? layer.position.x || 0 : 0;
var ty = (layer.position) ? layer.position.y || 0 : 0;
result = offset_to(result, 0, 0);
result.label = layer.name;
result.transform = layer.transform;
result.transform.tx = tx;
result.transform.ty = ty;
return result;
}
function is_opaque(layer) {
return layer.color == undefined || (layer.color.a || 0) > 0;
}
function is_empty(region) {
return region == undefined ||
region.rect == undefined ||
region.rect.length == 0 ||
region.rect.every(function(r) { return is_empty_rect(r) } );
}
/**
* Checks if the layer is visible on screen according to its type,
* active buffer content, alpha and visible regions.
*
* @param {layer} layer
* @returns if the layer is visible on screen or not
*/
function is_visible(layer) {
var visible = (layer.activeBuffer || layer.type === 'ColorLayer')
&& !hidden && is_opaque(layer);
visible &= !is_empty(layer.visibleRegion);
return visible;
}
function postprocess_flags(layer) {
if (!layer.flags) return;
var verboseFlags = [];
if (layer.flags & FLAG_HIDDEN) {
verboseFlags.push("HIDDEN");
}
if (layer.flags & FLAG_OPAQUE) {
verboseFlags.push("OPAQUE");
}
if (layer.flags & FLAG_SECURE) {
verboseFlags.push("SECURE");
}
layer.flags = verboseFlags.join('|') + " (" + layer.flags + ")";
} }
var chips = []; var chips = [];
var rect = transform_rect(layer); var rect = transform_bounds(layer, parentBounds);
var hidden = (layer.flags & FLAG_HIDDEN) != 0 || parentHidden; var hidden = (layer.flags & FLAG_HIDDEN) != 0 || parentHidden;
var visible = (layer.activeBuffer || layer.type === 'ColorLayer') var visible = is_visible(layer);
&& !hidden && layer.color.a > 0;
if (visible) { if (visible) {
chips.push(get_visible_chip()); chips.push(get_visible_chip());
} else { } else {
rect = undefined; rect = undefined;
} }
var bounds = undefined; var bounds = undefined;
if (layer.name.startsWith("Display Root#0")) { if (layer.name.startsWith("Display Root#0") && layer.sourceBounds) {
bounds = {width: layer.size.w, height: layer.size.h}; bounds = {width: layer.sourceBounds.right, height: layer.sourceBounds.bottom};
} }
if (layer.zOrderRelativeOf !== -1) {
if ((layer.zOrderRelativeOf || -1) !== -1) {
chips.push(RELATIVE_Z_CHIP); chips.push(RELATIVE_Z_CHIP);
} }
if (layer.zOrderRelativeParentOf !== undefined) { if (layer.zOrderRelativeParentOf !== undefined) {
@@ -67,7 +174,9 @@ function transform_layer(layer, {parentHidden}) {
} }
var transform_layer_with_parent_hidden = var transform_layer_with_parent_hidden =
(layer) => transform_layer(layer, {parentHidden: hidden}); (layer) => transform_layer(layer, {parentBounds: rect, parentHidden: hidden});
postprocess_flags(layer);
return transform({ return transform({
obj: layer, obj: layer,
@@ -89,16 +198,20 @@ function missingLayer(childId) {
name: "layer #" + childId, name: "layer #" + childId,
missing: true, missing: true,
zOrderRelativeOf: -1, zOrderRelativeOf: -1,
transform: {dsdx:1, dtdx:0, dsdy:0, dtdy:1},
} }
} }
function transform_layers(layers) { function transform_layers(layers) {
var idToItem = {}; var idToItem = {};
var isChild = {} var isChild = {}
layers.layers.forEach((e) => {
var layersList = layers.layers || [];
layersList.forEach((e) => {
idToItem[e.id] = e; idToItem[e.id] = e;
}); });
layers.layers.forEach((e) => { layersList.forEach((e) => {
e.resolvedChildren = []; e.resolvedChildren = [];
if (Array.isArray(e.children)) { if (Array.isArray(e.children)) {
e.resolvedChildren = e.children.map( e.resolvedChildren = e.children.map(
@@ -107,11 +220,12 @@ function transform_layers(layers) {
isChild[childId] = true; isChild[childId] = true;
}); });
} }
if (e.zOrderRelativeOf !== -1) { if ((e.zOrderRelativeOf || -1) !== -1) {
idToItem[e.zOrderRelativeOf].zOrderRelativeParentOf = e.id; idToItem[e.zOrderRelativeOf].zOrderRelativeParentOf = e.id;
} }
}); });
var roots = layers.layers.filter((e) => !isChild[e.id]);
var roots = layersList.filter((e) => !isChild[e.id]);
function foreachTree(nodes, fun) { function foreachTree(nodes, fun) {
nodes.forEach((n) => { nodes.forEach((n) => {
@@ -121,12 +235,15 @@ function transform_layers(layers) {
} }
var idToTransformed = {}; var idToTransformed = {};
var transformed_roots = roots.map(transform_layer); var transformed_roots = roots.map((r) =>
transform_layer(r, {parentBounds: {left: 0, right: 0, top: 0, bottom: 0},
parentHidden: false}));
foreachTree(transformed_roots, (n) => { foreachTree(transformed_roots, (n) => {
idToTransformed[n.obj.id] = n; idToTransformed[n.obj.id] = n;
}); });
var flattened = []; var flattened = [];
layers.layers.forEach((e) => { layersList.forEach((e) => {
flattened.push(idToTransformed[e.id]); flattened.push(idToTransformed[e.id]);
}); });
@@ -159,11 +276,12 @@ function transform_layers_entry(entry) {
[[entry.layers], transform_layers], [[entry.layers], transform_layers],
], ],
timestamp: entry.elapsedRealtimeNanos, timestamp: entry.elapsedRealtimeNanos,
stableId: 'entry',
}); });
} }
function transform_layers_trace(entries) { function transform_layers_trace(entries) {
return transform({ var r = transform({
obj: entries, obj: entries,
kind: 'layerstrace', kind: 'layerstrace',
name: 'layerstrace', name: 'layerstrace',
@@ -171,6 +289,8 @@ function transform_layers_trace(entries) {
[entries.entry, transform_layers_entry], [entries.entry, transform_layers_entry],
], ],
}); });
return r;
} }
export {transform_layers, transform_layers_trace}; export {transform_layers, transform_layers_trace};

View File

@@ -31,7 +31,7 @@ function transform_window(entry) {
} }
} }
var name = renderIdentifier(entry.identifier) var name = renderIdentifier(entry.identifier)
var rect = transform_rect(entry.windowFrames.frame, name); var rect = transform_rect((entry.windowFrames || entry).frame, name);
if (visible) { if (visible) {
chips.push(get_visible_chip()); chips.push(get_visible_chip());
@@ -182,6 +182,7 @@ function transform_entry(entry) {
[[entry.windowManagerService.policy], transform_policy], [[entry.windowManagerService.policy], transform_policy],
], ],
timestamp: entry.elapsedRealtimeNanos, timestamp: entry.elapsedRealtimeNanos,
stableId: 'entry',
}); });
} }