/* * Copyright (C) 2024 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 {Timestamp} from 'common/time/time'; import {TimeDuration} from 'common/time/time_duration'; import {RawDataUtils} from 'parsers/raw_data_utils'; import {TransformType} from 'parsers/surface_flinger/transform_utils'; import {CujType} from 'trace/cuj_type'; import {PropertyTreeNode} from './property_tree_node'; const EMPTY_OBJ_STRING = '{empty}'; const EMPTY_ARRAY_STRING = '[empty]'; function formatAsDecimal(value: number): string { if (!Number.isInteger(value)) { return value.toFixed(3).toString(); } return value.toString(); } function formatAsHex(value: number, upperCase = false): string { if (value < 0) { value += Math.pow(2, 32); // convert to 2's complement } let hexValue = value.toString(16); if (upperCase) { hexValue = hexValue.toUpperCase(); } return '0x' + hexValue; } interface PropertyFormatter { format(node: PropertyTreeNode): string; } class DefaultPropertyFormatter implements PropertyFormatter { format(node: PropertyTreeNode): string { const value = node.getValue(); if (Array.isArray(value) && value.length === 0) { return EMPTY_ARRAY_STRING; } if (typeof value === 'number') { return formatAsDecimal(value); } if (value?.toString) return value.toString(); return `${value}`; } } const DEFAULT_PROPERTY_FORMATTER = new DefaultPropertyFormatter(); class ColorFormatter implements PropertyFormatter { format(node: PropertyTreeNode): string { const rNode = node.getChildByName('r'); const gNode = node.getChildByName('g'); const bNode = node.getChildByName('b'); const alphaNode = node.getChildByName('a'); const r = formatAsDecimal(rNode?.getValue() ?? 0); const g = formatAsDecimal(gNode?.getValue() ?? 0); const b = formatAsDecimal(bNode?.getValue() ?? 0); const rgbString = `(${r}, ${g}, ${b})`; if (rNode && gNode && bNode && !alphaNode) { return rgbString; } const alpha = formatAsDecimal(alphaNode?.getValue() ?? 0); const alphaString = `alpha: ${alpha}`; if (RawDataUtils.isEmptyObj(node)) { return `${EMPTY_OBJ_STRING}, ${alphaString}`; } return `${rgbString}, ${alphaString}`; } } const COLOR_FORMATTER = new ColorFormatter(); class RectFormatter implements PropertyFormatter { format(node: PropertyTreeNode): string { if (!RawDataUtils.isRect(node) || RawDataUtils.isEmptyObj(node)) { return EMPTY_OBJ_STRING; } const left = formatAsDecimal(node.getChildByName('left')?.getValue() ?? 0); const top = formatAsDecimal(node.getChildByName('top')?.getValue() ?? 0); const right = formatAsDecimal( node.getChildByName('right')?.getValue() ?? 0, ); const bottom = formatAsDecimal( node.getChildByName('bottom')?.getValue() ?? 0, ); return `(${left}, ${top}) - (${right}, ${bottom})`; } } const RECT_FORMATTER = new RectFormatter(); class BufferFormatter implements PropertyFormatter { format(node: PropertyTreeNode): string { return `w: ${node.getChildByName('width')?.getValue() ?? 0}, h: ${ node.getChildByName('height')?.getValue() ?? 0 }, stride: ${node.getChildByName('stride')?.getValue()}, format: ${node .getChildByName('format') ?.getValue()}`; } } const BUFFER_FORMATTER = new BufferFormatter(); class LayerIdFormatter implements PropertyFormatter { format(node: PropertyTreeNode): string { const value = node.getValue(); return value === -1 || value === 0 ? 'none' : `${value}`; } } const LAYER_ID_FORMATTER = new LayerIdFormatter(); class MatrixFormatter implements PropertyFormatter { format(node: PropertyTreeNode): string { const dsdx = formatAsDecimal(node.getChildByName('dsdx')?.getValue() ?? 0); const dtdx = formatAsDecimal(node.getChildByName('dtdx')?.getValue() ?? 0); const dtdy = formatAsDecimal(node.getChildByName('dtdy')?.getValue() ?? 0); const dsdy = formatAsDecimal(node.getChildByName('dsdy')?.getValue() ?? 0); const tx = node.getChildByName('tx'); const ty = node.getChildByName('ty'); if ( dsdx === '0' && dtdx === '0' && dsdy === '0' && dtdy === '0' && !tx && !ty ) { return 'null'; } const matrix22 = `dsdx: ${dsdx}, dtdx: ${dtdx}, dtdy: ${dtdy}, dsdy: ${dsdy}`; if (!tx && !ty) { return matrix22; } return ( matrix22 + `, tx: ${formatAsDecimal(tx?.getValue() ?? 0)}, ty: ${formatAsDecimal( ty?.getValue() ?? 0, )}` ); } } const MATRIX_FORMATTER = new MatrixFormatter(); class TransformFormatter implements PropertyFormatter { format(node: PropertyTreeNode): string { const type = node.getChildByName('type'); return type !== undefined ? TransformType.getTypeFlags(type.getValue() ?? 0) : 'null'; } } const TRANSFORM_FORMATTER = new TransformFormatter(); class SizeFormatter implements PropertyFormatter { format(node: PropertyTreeNode): string { return `${node.getChildByName('w')?.getValue() ?? 0} x ${ node.getChildByName('h')?.getValue() ?? 0 }`; } } const SIZE_FORMATTER = new SizeFormatter(); class PositionFormatter implements PropertyFormatter { format(node: PropertyTreeNode): string { const x = formatAsDecimal(node.getChildByName('x')?.getValue() ?? 0); const y = formatAsDecimal(node.getChildByName('y')?.getValue() ?? 0); return `x: ${x}, y: ${y}`; } } const POSITION_FORMATTER = new PositionFormatter(); class RegionFormatter implements PropertyFormatter { format(node: PropertyTreeNode): string { let res = 'SkRegion('; node .getChildByName('rect') ?.getAllChildren() .forEach((rectNode: PropertyTreeNode) => { res += `(${rectNode.getChildByName('left')?.getValue() ?? 0}, ${ rectNode.getChildByName('top')?.getValue() ?? 0 }, ${rectNode.getChildByName('right')?.getValue() ?? 0}, ${ rectNode.getChildByName('bottom')?.getValue() ?? 0 })`; }); return res + ')'; } } const REGION_FORMATTER = new RegionFormatter(); class EnumFormatter implements PropertyFormatter { constructor(private readonly valuesById: {[key: number]: string}) {} format(node: PropertyTreeNode): string { const value = node.getValue(); if (typeof value === 'number' && this.valuesById[value]) { return this.valuesById[value]; } if (typeof value === 'bigint' && this.valuesById[Number(value)]) { return this.valuesById[Number(value)]; } return `${value}`; } } class FixedStringFormatter implements PropertyFormatter { constructor(private readonly fixedStringValue: string) {} format(node: PropertyTreeNode): string { return this.fixedStringValue; } } class TimestampNodeFormatter implements PropertyFormatter { format(node: PropertyTreeNode): string { const timestamp = node.getValue(); if (timestamp instanceof Timestamp || timestamp instanceof TimeDuration) { return timestamp.format(); } return 'null'; } } const TIMESTAMP_NODE_FORMATTER = new TimestampNodeFormatter(); class CujTypeFormatter implements PropertyFormatter { format(node: PropertyTreeNode): string { const cujTypeId: string = `${node.getValue()}`; let cujTypeString: string | undefined; if (cujTypeId in CujType) { cujTypeString = CujType[cujTypeId as keyof typeof CujType]; } else { cujTypeString = 'UNKNOWN'; } return `${cujTypeString} (${cujTypeId})`; } } const CUJ_TYPE_FORMATTER = new CujTypeFormatter(); class HexFormatter implements PropertyFormatter { format(node: PropertyTreeNode): string { return formatAsHex(node.getValue() ?? 0); } } const HEX_FORMATTER = new HexFormatter(); export { EMPTY_OBJ_STRING, EMPTY_ARRAY_STRING, formatAsHex, PropertyFormatter, DEFAULT_PROPERTY_FORMATTER, COLOR_FORMATTER, RECT_FORMATTER, BUFFER_FORMATTER, LAYER_ID_FORMATTER, TRANSFORM_FORMATTER, SIZE_FORMATTER, POSITION_FORMATTER, REGION_FORMATTER, EnumFormatter, FixedStringFormatter, TIMESTAMP_NODE_FORMATTER, MATRIX_FORMATTER, CUJ_TYPE_FORMATTER, HEX_FORMATTER, };