blob: 98f078d06577181ecd25fe6b7920c41fa33e7347 [file] [log] [blame]
/*
* Copyright 2021, 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 {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: 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)
}
}
}
})
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(' | ');
}
}