• 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 {toSize, toActiveBuffer, toColor, toColor3, toPoint, toRect,
18    toRectF, toRegion, toMatrix22, toTransform} from './common';
19import intDefMapping from
20    '../../../../../prebuilts/misc/common/winscope/intDefMapping.json';
21import config from '../config/Configuration.json'
22
23function readIntdefMap(): Map<string, string> {
24    const map = new Map<string, string>();
25    const keys = Object.keys(config.intDefColumn);
26
27    keys.forEach(key => {
28        const value = config.intDefColumn[key];
29        map.set(key, value);
30    });
31
32    return map;
33}
34export default class ObjectFormatter {
35    static displayDefaults: boolean = false
36    private static INVALID_ELEMENT_PROPERTIES = config.invalidProperties;
37
38    private static FLICKER_INTDEF_MAP = readIntdefMap();
39
40    static cloneObject(entry: any): any {
41        let obj: any = {}
42        const properties = ObjectFormatter.getProperties(entry);
43        properties.forEach(prop => obj[prop] = entry[prop]);
44        return obj;
45    }
46
47    /**
48     * Get the true properties of an entry excluding functions, kotlin gernerated
49     * variables, explicitly excluded properties, and flicker objects already in
50     * the hierarchy that shouldn't be traversed when formatting the entry
51     * @param entry The entry for which we want to get the properties for
52     * @return The "true" properties of the entry as described above
53     */
54    static getProperties(entry: any): string[] {
55        var props = [];
56        let obj = entry;
57
58        do {
59            const properties = Object.getOwnPropertyNames(obj).filter(it => {
60                // filter out functions
61                if (typeof(entry[it]) === 'function') return false;
62                // internal propertires from kotlinJs
63                if (it.includes(`$`)) return false;
64                // private kotlin variables from kotlin
65                if (it.startsWith(`_`)) return false;
66                // some predefined properties used only internally (e.g., children, ref, diff)
67                if (this.INVALID_ELEMENT_PROPERTIES.includes(it)) return false;
68
69                const value = entry[it];
70                // only non-empty arrays of non-flicker objects (otherwise they are in hierarchy)
71                if (Array.isArray(value) && value.length > 0) return !value[0].stableId;
72                // non-flicker object
73                return !(value?.stableId);
74            });
75            properties.forEach(function (prop) {
76                if (typeof(entry[prop]) !== 'function' && props.indexOf(prop) === -1) {
77                    props.push(prop);
78                }
79            });
80        } while (obj = Object.getPrototypeOf(obj));
81
82        return props;
83    }
84
85    /**
86     * Format a Winscope entry to be displayed in the UI
87     * Accounts for different user display settings (e.g. hiding empty/default values)
88     * @param obj The raw object to format
89     * @return The formatted object
90     */
91    static format(obj: any): {} {
92        const properties = this.getProperties(obj);
93        const sortedProperties = properties.sort()
94
95        const result: any = {}
96        sortedProperties.forEach(entry => {
97            const key = entry;
98            const value: any = obj[key];
99
100            if (value === null || value === undefined) {
101                if (this.displayDefaults) {
102                    result[key] = value;
103                }
104                return
105            }
106
107            if (value || this.displayDefaults) {
108                // raw values (e.g., false or 0)
109                if (!value) {
110                    result[key] = value
111                // flicker obj
112                } else if (value.prettyPrint) {
113                    const isEmpty = value.isEmpty === true;
114                    if (!isEmpty || this.displayDefaults) {
115                        result[key] = value.prettyPrint();
116                    }
117                } else {
118                    // converted proto to flicker
119                    const translatedObject = this.translateObject(value);
120                    if (translatedObject) {
121                        if (translatedObject.prettyPrint) {
122                          result[key] = translatedObject.prettyPrint();
123                        }
124                        else {
125                          result[key] = translatedObject;
126                        }
127                    // objects - recursive call
128                    } else if (value && typeof(value) == `object`) {
129                        const childObj = this.format(value) as any;
130                        const isEmpty = Object.entries(childObj).length == 0 || childObj.isEmpty;
131                        if (!isEmpty || this.displayDefaults) {
132                            result[key] = childObj;
133                        }
134                    } else {
135                    // values
136                        result[key] = this.translateIntDef(obj, key, value);
137                    }
138                }
139
140            }
141        })
142
143        return result;
144    }
145
146    /**
147     * Translate some predetermined proto objects into their flicker equivalent
148     *
149     * Returns null if the object cannot be translated
150     *
151     * @param obj Object to translate
152     */
153    private static translateObject(obj) {
154        const type = obj?.$type?.name;
155        switch(type) {
156            case `SizeProto`: return toSize(obj);
157            case `ActiveBufferProto`: return toActiveBuffer(obj);
158            case `Color3`: return toColor3(obj);
159            case `ColorProto`: return toColor(obj);
160            case `PointProto`: return toPoint(obj);
161            case `RectProto`: return toRect(obj);
162            case `Matrix22`: return toMatrix22(obj);
163            case `FloatRectProto`: return toRectF(obj);
164            case `RegionProto`: return toRegion(obj);
165            case `TransformProto`: return toTransform(obj);
166            case 'ColorTransformProto': {
167                const formatted = this.formatColorTransform(obj.val);
168                return `${formatted}`;
169            }
170        }
171
172        return null;
173    }
174
175    private static formatColorTransform(vals) {
176        const fixedVals = vals.map((v) => v.toFixed(1));
177        let formatted = ``;
178        for (let i = 0; i < fixedVals.length; i += 4) {
179            formatted += `[`;
180            formatted += fixedVals.slice(i, i + 4).join(', ');
181            formatted += `] `;
182        }
183        return formatted;
184    }
185
186    /**
187     * Obtains from the proto field, the metadata related to the typedef type (if any)
188     *
189     * @param obj Proto object
190     * @param propertyName Property to search
191     */
192    private static getTypeDefSpec(obj: any, propertyName: string): string {
193        const fields = obj?.$type?.fields;
194        if (!fields) {
195            return null;
196        }
197
198        const options = fields[propertyName]?.options;
199        if (!options) {
200            return null;
201        }
202
203        return options["(.android.typedef)"];
204    }
205
206    /**
207     * Translate intdef properties into their string representation
208     *
209     * For proto objects check the
210     *
211     * @param parentObj Object containing the value to parse
212     * @param propertyName Property to search
213     * @param value Property value
214     */
215    private static translateIntDef(parentObj: any, propertyName: string, value: any): string {
216        const parentClassName = parentObj.constructor.name;
217        const propertyPath = `${parentClassName}.${propertyName}`;
218
219        let translatedValue = value;
220        // Parse Flicker objects (no intdef annotation supported)
221        if (this.FLICKER_INTDEF_MAP.has(propertyPath)) {
222            translatedValue = this.getIntFlagsAsStrings(value,
223                this.FLICKER_INTDEF_MAP.get(propertyPath));
224        } else {
225            // If it's a proto, search on the proto definition for the intdef type
226            const typeDefSpec = this.getTypeDefSpec(parentObj, propertyName);
227            if (typeDefSpec) {
228                translatedValue = this.getIntFlagsAsStrings(value, typeDefSpec);
229            }
230        }
231
232        return translatedValue;
233    }
234
235    /**
236     * Translate a property from its numerical value into its string representation
237     *
238     * @param intFlags Property value
239     * @param annotationType IntDef type to use
240     */
241    private static getIntFlagsAsStrings(intFlags: any, annotationType: string) {
242        const flags = [];
243
244        const mapping = intDefMapping[annotationType].values;
245        const knownFlagValues = Object.keys(mapping).reverse().map(x => parseInt(x));
246
247        if (mapping.length == 0) {
248            console.warn("No mapping for type", annotationType)
249            return intFlags + ""
250        }
251
252        // Will only contain bits that have not been associated with a flag.
253        const parsedIntFlags = parseInt(intFlags);
254        let leftOver = parsedIntFlags;
255
256        for (const flagValue of knownFlagValues) {
257          if (((leftOver & flagValue) && ((intFlags & flagValue) === flagValue))
258                || (parsedIntFlags === 0 && flagValue === 0)) {
259            flags.push(mapping[flagValue]);
260
261            leftOver = leftOver & ~flagValue;
262          }
263        }
264
265        if (flags.length === 0) {
266          console.error('No valid flag mappings found for ',
267              intFlags, 'of type', annotationType);
268        }
269
270        if (leftOver) {
271          // If 0 is a valid flag value that isn't in the intDefMapping
272          // it will be ignored
273          flags.push(leftOver);
274        }
275
276        return flags.join(' | ');
277      }
278}
279