• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright 2021, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import {toBounds, toBuffer, toColor, toPoint, toRect,
18    toRectF, toRegion, toTransform} from './common';
19import intDefMapping from
20    '../../../../../prebuilts/misc/common/winscope/intDefMapping.json';
21
22export default class ObjectFormatter {
23    private static INVALID_ELEMENT_PROPERTIES = ['length', 'name', 'prototype', 'children',
24    'childrenWindows', 'ref', 'root', 'layers', 'resolvedChildren']
25
26    private static FLICKER_INTDEF_MAP = new Map([
27        [`WindowLayoutParams.type`, `android.view.WindowManager.LayoutParams.WindowType`],
28        [`WindowLayoutParams.flags`, `android.view.WindowManager.LayoutParams.Flags`],
29        [`WindowLayoutParams.privateFlags`, `android.view.WindowManager.LayoutParams.PrivateFlags`],
30        [`WindowLayoutParams.gravity`, `android.view.Gravity.GravityFlags`],
31        [`WindowLayoutParams.softInputMode`, `android.view.WindowManager.LayoutParams.WindowType`],
32        [`WindowLayoutParams.systemUiVisibilityFlags`, `android.view.WindowManager.LayoutParams.SystemUiVisibilityFlags`],
33        [`WindowLayoutParams.subtreeSystemUiVisibilityFlags`, `android.view.WindowManager.LayoutParams.SystemUiVisibilityFlags`],
34        [`WindowLayoutParams.behavior`, `android.view.WindowInsetsController.Behavior`],
35        [`WindowLayoutParams.fitInsetsSides`, `android.view.WindowInsets.Side.InsetsSide`],
36
37        [`Configuration.windowingMode`, `android.app.WindowConfiguration.WindowingMode`],
38        [`WindowConfiguration.windowingMode`, `android.app.WindowConfiguration.WindowingMode`],
39        [`Configuration.orientation`, `android.content.pm.ActivityInfo.ScreenOrientation`],
40        [`WindowConfiguration.orientation`, `android.content.pm.ActivityInfo.ScreenOrientation`],
41        [`WindowState.orientation`, `android.content.pm.ActivityInfo.ScreenOrientation`],
42    ])
43
44    static format(obj: any): {} {
45        const entries = Object.entries(obj)
46            .filter(it => !it[0].includes(`$`))
47            .filter(it => !this.INVALID_ELEMENT_PROPERTIES.includes(it[0]))
48        const sortedEntries = entries.sort()
49
50        const result: any = {}
51        sortedEntries.forEach(entry => {
52            const key = entry[0]
53            const value: any = entry[1]
54
55            if (value) {
56                // flicker obj
57                if (value.prettyPrint) {
58                    result[key] = value.prettyPrint()
59                } else {
60                    // converted proto to flicker
61                    const translatedObject = this.translateObject(value)
62                    if (translatedObject) {
63                        result[key] = translatedObject.prettyPrint()
64                    // objects - recursive call
65                    } else if (value && typeof(value) == `object`) {
66                        result[key] = this.format(value)
67                    } else {
68                    // values
69                        result[key] = this.translateIntDef(obj, key, value)
70                    }
71                }
72
73            }
74        })
75
76        return Object.freeze(result)
77    }
78
79    /**
80     * Translate some predetermined proto objects into their flicker equivalent
81     *
82     * Returns null if the object cannot be translated
83     *
84     * @param obj Object to translate
85     */
86    private static translateObject(obj) {
87        const type = obj?.$type?.name
88        switch(type) {
89            case `SizeProto`: return toBounds(obj)
90            case `ActiveBufferProto`: return toBuffer(obj)
91            case `ColorProto`: return toColor(obj)
92            case `PointProto`: return toPoint(obj)
93            case `RectProto`: return toRect(obj)
94            case `FloatRectProto`: return toRectF(obj)
95            case `RegionProto`: return toRegion(obj)
96            case `TransformProto`: return toTransform(obj)
97            case 'ColorTransformProto': {
98                const formatted = this.formatColorTransform(obj.val);
99                return `${formatted}`;
100            }
101        }
102
103        return null
104    }
105
106    private static formatColorTransform(vals) {
107        const fixedVals = vals.map((v) => v.toFixed(1));
108        let formatted = ``;
109        for (let i = 0; i < fixedVals.length; i += 4) {
110            formatted += `[`;
111            formatted += fixedVals.slice(i, i + 4).join(', ');
112            formatted += `] `;
113        }
114        return formatted;
115    }
116
117    /**
118     * Obtains from the proto field, the metadata related to the typedef type (if any)
119     *
120     * @param obj Proto object
121     * @param propertyName Property to search
122     */
123    private static getTypeDefSpec(obj: any, propertyName: string): string {
124        const fields = obj?.$type?.fields
125        if (!fields) {
126            return null
127        }
128
129        const options = fields[propertyName]?.options
130        if (!options) {
131            return null
132        }
133
134        return options["(.android.typedef)"]
135    }
136
137    /**
138     * Translate intdef properties into their string representation
139     *
140     * For proto objects check the
141     *
142     * @param parentObj Object containing the value to parse
143     * @param propertyName Property to search
144     * @param value Property value
145     */
146    private static translateIntDef(parentObj: any, propertyName: string, value: any): string {
147        const parentClassName = parentObj.constructor.name
148        const propertyPath = `${parentClassName}.${propertyName}`
149
150        let translatedValue = value
151        // Parse Flicker objects (no intdef annotation supported)
152        if (this.FLICKER_INTDEF_MAP.has(propertyPath)) {
153            translatedValue = this.getIntFlagsAsStrings(value,
154                this.FLICKER_INTDEF_MAP.get(propertyPath))
155        } else {
156            // If it's a proto, search on the proto definition for the intdef type
157            const typeDefSpec = this.getTypeDefSpec(parentObj, propertyName)
158            if (typeDefSpec) {
159                translatedValue = this.getIntFlagsAsStrings(value, typeDefSpec)
160            }
161        }
162
163        return translatedValue
164    }
165
166    /**
167     * Translate a property from its numerical value into its string representation
168     *
169     * @param intFlags Property value
170     * @param annotationType IntDef type to use
171     */
172    private static getIntFlagsAsStrings(intFlags: any, annotationType: string) {
173        const flags = [];
174
175        const mapping = intDefMapping[annotationType].values;
176        const knownFlagValues = Object.keys(mapping).reverse().map(x => parseInt(x));
177
178        if (mapping.length == 0) {
179            console.warn("No mapping for type", annotationType)
180            return intFlags + ""
181        }
182
183        // Will only contain bits that have not been associated with a flag.
184        const parsedIntFlags = parseInt(intFlags);
185        let leftOver = parsedIntFlags;
186
187        for (const flagValue of knownFlagValues) {
188          if (((leftOver & flagValue) && ((intFlags & flagValue) === flagValue))
189                || (parsedIntFlags === 0 && flagValue === 0)) {
190            flags.push(mapping[flagValue]);
191
192            leftOver = leftOver & ~flagValue;
193          }
194        }
195
196        if (flags.length === 0) {
197          console.error('No valid flag mappings found for ',
198              intFlags, 'of type', annotationType);
199        }
200
201        if (leftOver) {
202          // If 0 is a valid flag value that isn't in the intDefMapping
203          // it will be ignored
204          flags.push(leftOver);
205        }
206
207        return flags.join(' | ');
208      }
209}