Merge "5/ Show Flicker properties on Winscope (WM)" into sc-dev am: 1d6a084cc9

Original change: https://googleplex-android-review.googlesource.com/c/platform/development/+/14294778

Change-Id: Id789dd64ea3623547b41b5102d8533695d6eebd0
This commit is contained in:
Nataniel Borges
2021-05-05 18:12:06 +00:00
committed by Automerger Merge Worker
13 changed files with 302 additions and 136 deletions

View File

@@ -120,49 +120,13 @@ import PropertiesTreeElement from './PropertiesTreeElement.vue';
import {ObjectTransformer} from './transform.js';
import {DiffGenerator, defaultModifiedCheck} from './utils/diff.js';
// eslint-disable-next-line camelcase
import {format_transform_type, is_simple_transform} from './matrix_utils.js';
import {TRACE_TYPES, DUMP_TYPES} from './decode.js';
import {stableIdCompatibilityFixup} from './utils/utils.js';
import {CompatibleFeatures} from './utils/compatibility.js';
function formatColorTransform(vals) {
const fixedVals = vals.map((v) => v.toFixed(1));
let formatted = ``;
for (let i = 0; i < fixedVals.length; i += 4) {
formatted += `[`;
formatted += fixedVals.slice(i, i + 4).join(', ');
formatted += `] `;
}
return formatted;
}
function formatProto(obj) {
if (!obj || !obj.$type) {
return;
}
if (obj.$type.name === 'RectProto') {
return `(${obj.left}, ${obj.top}) - (${obj.right}, ${obj.bottom})`;
} else if (obj.$type.name === 'FloatRectProto') {
return `(${obj.left.toFixed(3)}, ${obj.top.toFixed(3)}) - ` +
`(${obj.right.toFixed(3)}, ${obj.bottom.toFixed(3)})`;
} else if (obj.$type.name === 'PositionProto') {
return `(${obj.x.toFixed(3)}, ${obj.y.toFixed(3)})`;
} else if (obj.$type.name === 'SizeProto') {
return `${obj.w} x ${obj.h}`;
} else if (obj.$type.name === 'ColorProto') {
return `r:${obj.r} g:${obj.g} \n b:${obj.b} a:${obj.a}`;
} else if (obj.$type.name === 'TransformProto') {
const transformType = format_transform_type(obj);
if (is_simple_transform(obj)) {
return `${transformType}`;
}
return `${transformType} dsdx:${obj.dsdx.toFixed(3)} ` +
`dtdx:${obj.dtdx.toFixed(3)} dsdy:${obj.dsdy.toFixed(3)} ` +
`dtdy:${obj.dtdy.toFixed(3)}`;
} else if (obj.$type.name === 'ColorTransformProto') {
const formated = formatColorTransform(obj.val);
return `${formated}`;
if (obj?.prettyPrint) {
return obj.prettyPrint();
}
}

View File

@@ -14,24 +14,196 @@
* limitations under the License.
*/
import {toBounds, toBuffer, toColor, toPoint, toRect,
toRectF, toRegion, toTransform} from './common';
import intDefMapping from
'../../../../../prebuilts/misc/common/winscope/intDefMapping.json';
export default class ObjectFormatter {
private static INVALID_ELEMENT_PROPERTIES = ['length', 'name', 'prototype', 'children',
'childrenWindows', 'ref', 'root', 'layers', 'resolvedChildren']
private static FLICKER_INTDEF_MAP = new Map([
[`WindowLayoutParams.type`, `android.view.WindowManager.LayoutParams.WindowType`],
[`WindowLayoutParams.flags`, `android.view.WindowManager.LayoutParams.Flags`],
[`WindowLayoutParams.privateFlags`, `android.view.WindowManager.LayoutParams.PrivateFlags`],
[`WindowLayoutParams.gravity`, `android.view.Gravity.GravityFlags`],
[`WindowLayoutParams.softInputMode`, `android.view.WindowManager.LayoutParams.WindowType`],
[`WindowLayoutParams.systemUiVisibilityFlags`, `android.view.WindowManager.LayoutParams.SystemUiVisibilityFlags`],
[`WindowLayoutParams.subtreeSystemUiVisibilityFlags`, `android.view.WindowManager.LayoutParams.SystemUiVisibilityFlags`],
[`WindowLayoutParams.behavior`, `android.view.WindowInsetsController.Behavior`],
[`WindowLayoutParams.fitInsetsSides`, `android.view.WindowInsets.Side.InsetsSide`],
[`Configuration.windowingMode`, `android.app.WindowConfiguration.WindowingMode`],
[`WindowConfiguration.windowingMode`, `android.app.WindowConfiguration.WindowingMode`],
[`Configuration.orientation`, `android.content.pm.ActivityInfo.ScreenOrientation`],
[`WindowConfiguration.orientation`, `android.content.pm.ActivityInfo.ScreenOrientation`],
[`WindowState.orientation`, `android.content.pm.ActivityInfo.ScreenOrientation`],
])
static format(obj: any): {} {
const entries = Object.entries(obj)
.filter(it => !it[0].includes(`$`))
.filter(it => !this.INVALID_ELEMENT_PROPERTIES.includes(it[0]))
const sortedEntries = entries.sort()
const result: any = {}
sortedEntries.forEach(entry => {
const key = entry[0]
const value = entry[1]
if (value && typeof(value) == `object`) {
result[key] = this.format(value)
} else {
result[key] = value
const value: any = entry[1]
if (value) {
// flicker obj
if (value.prettyPrint) {
result[key] = value.prettyPrint()
} else {
// converted proto to flicker
const translatedObject = this.translateObject(value)
if (translatedObject) {
result[key] = translatedObject.prettyPrint()
// objects - recursive call
} else if (value && typeof(value) == `object`) {
result[key] = this.format(value)
} else {
// values
result[key] = this.translateIntDef(obj, key, value)
}
}
}
})
// Reassign prototype to ensure formatters work
result.__proto__ = obj.__proto__
return result
return Object.freeze(result)
}
/**
* Translate some predetermined proto objects into their flicker equivalent
*
* Returns null if the object cannot be translated
*
* @param obj Object to translate
*/
private static translateObject(obj) {
const type = obj?.$type?.name
switch(type) {
case `SizeProto`: return toBounds(obj)
case `ActiveBufferProto`: return toBuffer(obj)
case `ColorProto`: return toColor(obj)
case `PointProto`: return toPoint(obj)
case `RectProto`: return toRect(obj)
case `FloatRectProto`: return toRectF(obj)
case `RegionProto`: return toRegion(obj)
case `TransformProto`: return toTransform(obj)
case 'ColorTransformProto': {
const formatted = this.formatColorTransform(obj.val);
return `${formatted}`;
}
}
return null
}
private static formatColorTransform(vals) {
const fixedVals = vals.map((v) => v.toFixed(1));
let formatted = ``;
for (let i = 0; i < fixedVals.length; i += 4) {
formatted += `[`;
formatted += fixedVals.slice(i, i + 4).join(', ');
formatted += `] `;
}
return formatted;
}
/**
* Obtains from the proto field, the metadata related to the typedef type (if any)
*
* @param obj Proto object
* @param propertyName Property to search
*/
private static getTypeDefSpec(obj: any, propertyName: string): string {
const fields = obj?.$type?.fields
if (!fields) {
return null
}
const options = fields[propertyName]?.options
if (!options) {
return null
}
return options["(.android.typedef)"]
}
/**
* Translate intdef properties into their string representation
*
* For proto objects check the
*
* @param parentObj Object containing the value to parse
* @param propertyName Property to search
* @param value Property value
*/
private static translateIntDef(parentObj: any, propertyName: string, value: any): string {
const parentClassName = parentObj.constructor.name
const propertyPath = `${parentClassName}.${propertyName}`
let translatedValue = value
// Parse Flicker objects (no intdef annotation supported)
if (this.FLICKER_INTDEF_MAP.has(propertyPath)) {
translatedValue = this.getIntFlagsAsStrings(value,
this.FLICKER_INTDEF_MAP.get(propertyPath))
} else {
// If it's a proto, search on the proto definition for the intdef type
const typeDefSpec = this.getTypeDefSpec(parentObj, propertyName)
if (typeDefSpec) {
translatedValue = this.getIntFlagsAsStrings(value, typeDefSpec)
}
}
return translatedValue
}
/**
* Translate a property from its numerical value into its string representation
*
* @param intFlags Property value
* @param annotationType IntDef type to use
*/
private static getIntFlagsAsStrings(intFlags: any, annotationType: string) {
const flags = [];
const mapping = intDefMapping[annotationType].values;
const knownFlagValues = Object.keys(mapping).reverse().map(x => parseInt(x));
if (mapping.length == 0) {
console.warn("No mapping for type", annotationType)
return intFlags + ""
}
// Will only contain bits that have not been associated with a flag.
const parsedIntFlags = parseInt(intFlags);
let leftOver = parsedIntFlags;
for (const flagValue of knownFlagValues) {
if (((leftOver & flagValue) && ((intFlags & flagValue) === flagValue))
|| (parsedIntFlags === 0 && flagValue === 0)) {
flags.push(mapping[flagValue]);
leftOver = leftOver & ~flagValue;
}
}
if (flags.length === 0) {
console.error('No valid flag mappings found for ',
intFlags, 'of type', annotationType);
}
if (leftOver) {
// If 0 is a valid flag value that isn't in the intDefMapping
// it will be ignored
flags.push(leftOver);
}
return flags.join(' | ');
}
}

View File

@@ -15,8 +15,7 @@
*/
import { asRawTreeViewObject } from '../utils/diff.js'
import { nanosToString, TimeUnits } from "../utils/utils.js"
import { getWMPropertiesForDisplay } from './mixin'
import { getPropertiesForDisplay } from './mixin'
import {
KeyguardControllerState,
@@ -52,11 +51,10 @@ WindowManagerState.fromProto = function ({proto, timestamp = 0, where = ""}): Wi
entry.kind = entry.constructor.name
entry.rects = entry.windowStates.reverse().map(it => it.rect)
entry.obj = getWMPropertiesForDisplay(proto)
entry.obj["isComplete"] = entry.isComplete()
if (!entry.obj.isComplete) {
entry.obj["isIncompleteReason"] = entry.getIsIncompleteReason()
if (!entry.isComplete()) {
entry.isIncompleteReason = entry.getIsIncompleteReason()
}
entry.obj = getPropertiesForDisplay(proto, entry)
entry.shortName = entry.name
entry.chips = []
entry.visible = true

View File

@@ -51,14 +51,81 @@ const WindowState = require('flicker').com.android.server.wm.traces.common.
const WindowToken = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.WindowToken;
const Rect = require('flicker').com.android.server.wm.traces.common.Rect;
const Matrix = require('flicker').com.android.server.wm.traces.common.layers.Transform.Matrix;
const Transform = require('flicker').com.android.server.wm.traces.common.layers.Transform;
const Bounds = require('flicker').com.android.server.wm.traces.common.Bounds;
const Buffer = require('flicker').com.android.server.wm.traces.common.Buffer;
const Color = require('flicker').com.android.server.wm.traces.common.Color;
const Point = require('flicker').com.android.server.wm.traces.common.Point;
const Rect = require('flicker').com.android.server.wm.traces.common.Rect;
const RectF = require('flicker').com.android.server.wm.traces.common.RectF;
const Region = require('flicker').com.android.server.wm.traces.common.Region;
function toBounds(proto) {
if (proto == null) {
return null
}
return new Bounds(proto.width ?? proto.w ?? 0, proto.height ?? proto.h ?? 0)
}
function toBuffer(proto) {
if (proto == null) {
return null
}
return new Buffer(proto.width ?? 0, proto.height ?? 0, proto.stride ?? 0, proto.format ?? 0)
}
function toColor(proto) {
if (proto == null) {
return null
}
return new Color(proto.r ?? 0, proto.g ?? 0, proto.b ?? 0, proto.a ?? 0)
}
function toPoint(proto) {
if (proto == null) {
return null
}
return new Point(proto.x ?? 0, proto.y ?? 0)
}
function toRect(proto) {
if (proto == null) {
return null
}
return new Rect(proto.left, proto.top, proto.right, proto.bottom)
return new Rect(proto.left ?? 0, proto.top ?? 0, proto.right ?? 0, proto.bottom ?? 0)
}
function toRectF(proto) {
if (proto == null) {
return null
}
return new RectF(proto.left ?? 0, proto.top ?? 0, proto.right ?? 0, proto.bottom ?? 0)
}
function toRegion(proto) {
if (proto == null) {
return null
}
let rects = []
for (let rectNr in proto.rect) {
const rect = proto.rect[rectNr]
const parsedRect = toRect(rect)
rects.push(parsedRect)
}
return new Region(rects)
}
function toTransform(proto) {
if (proto == null) {
return null
}
const matrix = new Matrix(proto.dsdx ?? 0, proto.dtdx ?? 0,
proto.tx ?? 0, proto.dsdy ?? 0, proto.dtdy ?? 0, proto.ty ?? 0)
return new Transform(proto.type ?? 0, matrix)
}
export {
@@ -78,7 +145,19 @@ export {
WindowManagerPolicy,
WindowManagerTrace,
WindowManagerState,
Rect,
Bounds,
toRect
Buffer,
Color,
Point,
Rect,
RectF,
Region,
toBounds,
toBuffer,
toColor,
toPoint,
toRect,
toRectF,
toRegion,
toTransform
};

View File

@@ -22,18 +22,23 @@ import ObjectFormatter from "./ObjectFormatter"
* @param entry WM hierarchy element
* @param proto Associated proto object
*/
export function getWMPropertiesForDisplay(proto: any): any {
const obj = Object.assign({}, proto)
export function getPropertiesForDisplay(proto: any, entry: any): any {
let obj = Object.assign({}, entry)
if (obj.children) delete obj.children
if (obj.childWindows) delete obj.childWindows
if (obj.childrenWindows) delete obj.childrenWindows
if (obj.childContainers) delete obj.childContainers
if (obj.windowToken) delete obj.windowToken
if (obj.rootDisplayArea) delete obj.rootDisplayArea
if (obj.rootWindowContainer) delete obj.rootWindowContainer
if (obj.windowContainer?.children) delete obj.windowContainer.children
// obj = ObjectFormatter.format(obj)
return ObjectFormatter.format(obj)
obj.proto = Object.assign({}, proto)
if (obj.proto.children) delete obj.proto.children
if (obj.proto.childWindows) delete obj.proto.childWindows
if (obj.proto.childrenWindows) delete obj.proto.childrenWindows
if (obj.proto.childContainers) delete obj.proto.childContainers
if (obj.proto.windowToken) delete obj.proto.windowToken
if (obj.proto.rootDisplayArea) delete obj.proto.rootDisplayArea
if (obj.proto.rootWindowContainer) delete obj.proto.rootWindowContainer
if (obj.proto.windowContainer?.children) delete obj.proto.windowContainer.children
obj = ObjectFormatter.format(obj)
return obj
}
export function shortenName(name: any): string {
@@ -43,4 +48,4 @@ export function shortenName(name: any): string {
}
const className = classParts.slice(-1)[0] // last element
return `${classParts[0]}.${classParts[1]}.(...).${className}`
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { getWMPropertiesForDisplay, shortenName } from '../mixin'
import { getPropertiesForDisplay, shortenName } from '../mixin'
import { asRawTreeViewObject } from '../../utils/diff.js'
import { Activity } from "../common"
import WindowContainer from "./WindowContainer"
@@ -41,7 +41,7 @@ Activity.fromProto = function (proto): Activity {
windowContainer
)
entry.obj = getWMPropertiesForDisplay(proto)
entry.obj = getPropertiesForDisplay(proto, entry)
entry.kind = entry.constructor.name
entry.shortName = shortenName(entry.name)
entry.rawTreeViewObject = asRawTreeViewObject(entry)

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { getWMPropertiesForDisplay, shortenName } from '../mixin'
import { getPropertiesForDisplay, shortenName } from '../mixin'
import { asRawTreeViewObject } from '../../utils/diff.js'
import { ActivityTask, toRect } from "../common"
import WindowContainer from "./WindowContainer"
@@ -52,7 +52,7 @@ ActivityTask.fromProto = function (proto, isActivityInTree: Boolean): ActivityTa
windowContainer
)
entry.obj = getWMPropertiesForDisplay(proto)
entry.obj = getPropertiesForDisplay(proto, entry)
entry.kind = entry.constructor.name
entry.shortName = shortenName(entry.name)
entry.rawTreeViewObject = asRawTreeViewObject(entry)

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { getWMPropertiesForDisplay, shortenName } from '../mixin'
import { getPropertiesForDisplay, shortenName } from '../mixin'
import { asRawTreeViewObject } from '../../utils/diff.js'
import { DisplayArea } from "../common"
import WindowContainer from "./WindowContainer"
@@ -33,7 +33,7 @@ DisplayArea.fromProto = function (proto, isActivityInTree: Boolean): DisplayArea
}
const entry = new DisplayArea(proto.isTaskDisplayArea, windowContainer)
entry.obj = getWMPropertiesForDisplay(proto)
entry.obj = getPropertiesForDisplay(proto, entry)
entry.kind = entry.constructor.name
entry.shortName = shortenName(entry.name)
entry.rawTreeViewObject = asRawTreeViewObject(entry)

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { getWMPropertiesForDisplay, shortenName } from '../mixin'
import { getPropertiesForDisplay, shortenName } from '../mixin'
import { asRawTreeViewObject } from '../../utils/diff.js'
import { toRect, DisplayContent, Rect } from "../common"
import WindowContainer from "./WindowContainer"
@@ -61,7 +61,7 @@ DisplayContent.fromProto = function (proto, isActivityInTree: Boolean): DisplayC
windowContainer
)
entry.obj = getWMPropertiesForDisplay(proto)
entry.obj = getPropertiesForDisplay(proto, entry)
entry.kind = entry.constructor.name
entry.shortName = shortenName(entry.name)
entry.rawTreeViewObject = asRawTreeViewObject(entry)

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { getWMPropertiesForDisplay, shortenName } from '../mixin'
import { getPropertiesForDisplay, shortenName } from '../mixin'
import { asRawTreeViewObject } from '../../utils/diff.js'
import {
@@ -60,7 +60,7 @@ WindowContainer.fromProto = function ({
// we remove the children property from the object to avoid it showing the
// the properties view of the element as we can always see those elements'
// properties by changing the target element in the hierarchy tree view.
entry.obj = getWMPropertiesForDisplay(proto)
entry.obj = getPropertiesForDisplay(proto, entry)
entry.kind = entry.constructor.name
entry.shortName = shortenName(entry.name)
entry.rawTreeViewObject = asRawTreeViewObject(entry)

View File

@@ -14,9 +14,9 @@
* limitations under the License.
*/
import { getWMPropertiesForDisplay, shortenName } from '../mixin'
import { getPropertiesForDisplay, shortenName } from '../mixin'
import { asRawTreeViewObject } from '../../utils/diff.js'
import { toRect, WindowState, WindowLayoutParams } from "../common"
import { toRect, Bounds, WindowState, WindowLayoutParams } from "../common"
import { VISIBLE_CHIP } from '../treeview/Chips'
import WindowContainer from "./WindowContainer"
@@ -62,6 +62,8 @@ import WindowContainer from "./WindowContainer"
proto.animator?.surface?.layer ?? 0,
proto.animator?.surface?.shown ?? false,
windowType,
new Bounds(proto.requestedWidth, proto.requestedHeight),
toRect(proto.surfacePosition),
toRect(proto.windowFrames?.frame ?? null),
toRect(proto.windowFrames?.containingFrame ?? null),
toRect(proto.windowFrames?.parentFrame ?? null),
@@ -75,10 +77,10 @@ import WindowContainer from "./WindowContainer"
)
entry.kind = entry.constructor.name
entry.obj = getWMPropertiesForDisplay(proto)
entry.rect = entry.frame
entry.rect.ref = entry
entry.rect.label = entry.name
entry.obj = getPropertiesForDisplay(proto, entry)
entry.shortName = shortenName(entry.name)
entry.visible = entry.isVisible ?? false
entry.chips = entry.isVisible ? [VISIBLE_CHIP] : []

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { getWMPropertiesForDisplay, shortenName } from '../mixin'
import { getPropertiesForDisplay, shortenName } from '../mixin'
import { asRawTreeViewObject } from '../../utils/diff.js'
import { WindowToken } from "../common"
import WindowContainer from "./WindowContainer"
@@ -34,7 +34,7 @@ WindowToken.fromProto = function (proto, isActivityInTree: Boolean): WindowToken
}
const entry = new WindowToken(windowContainer)
entry.kind = entry.constructor.name
entry.obj = getWMPropertiesForDisplay(proto)
entry.obj = getPropertiesForDisplay(proto, entry)
entry.shortName = shortenName(entry.name)
entry.rawTreeViewObject = asRawTreeViewObject(entry)

View File

@@ -15,8 +15,6 @@
*/
import {DiffType} from './utils/diff.js';
import intDefMapping from
'../../../../prebuilts/misc/common/winscope/intDefMapping.json';
// kind - a type used for categorization of different levels
// name - name of the node
@@ -339,20 +337,6 @@ class ObjectTransformer {
fieldOptionsToUse = compareWithFieldOptions;
}
}
const annotationType = fieldOptionsToUse?.['(.android.typedef)'];
if (annotationType) {
if (intDefMapping[annotationType] === undefined) {
console.error(
`Missing intDef mapping for translation for ${annotationType}`);
} else if (intDefMapping[annotationType].flag) {
transformedObj.name = `${getIntFlagsAsStrings(
transformedObj.name, annotationType)} (${transformedObj.name})`;
} else {
transformedObj.name = `${intDefMapping[annotationType]
.values[transformedObj.name]} (${transformedObj.name})`;
}
}
}
if (transformOptions.keepOriginal) {
@@ -368,44 +352,6 @@ class ObjectTransformer {
}
}
function getIntFlagsAsStrings(intFlags, annotationType) {
const flags = [];
const mapping = intDefMapping[annotationType].values;
const knownFlagValues = Object.keys(mapping).reverse().map(x => parseInt(x));
if (mapping.length == 0) {
console.warn("No mapping for type", annotationType)
return intFlags + ""
}
// Will only contain bits that have not been associated with a flag.
const parsedIntFlags = parseInt(intFlags);
let leftOver = parsedIntFlags;
for (const flagValue of knownFlagValues) {
if (((leftOver & flagValue) && ((intFlags & flagValue) === flagValue))
|| (parsedIntFlags === 0 && flagValue === 0)) {
flags.push(mapping[flagValue]);
leftOver = leftOver & ~flagValue;
}
}
if (flags.length === 0) {
console.error('No valid flag mappings found for ',
intFlags, 'of type', annotationType);
}
if (leftOver) {
// If 0 is a valid flag value that isn't in the intDefMapping
// it will be ignored
flags.push(leftOver);
}
return flags.join(' | ');
}
// eslint-disable-next-line camelcase
function nanos_to_string(elapsedRealtimeNanos) {
const units = [