1/*
2 * Copyright (C) 2024 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 intDefMapping from 'common/intDefMapping.json';
18import {TamperedProtoField} from 'parsers/tampered_message_type';
19import {FixedStringFormatter} from 'trace/tree_node/formatters';
20import {Operation} from 'trace/tree_node/operations/operation';
21import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
22
23export class TranslateIntDef implements Operation<PropertyTreeNode> {
24  constructor(private readonly rootField: TamperedProtoField) {}
25
26  apply(value: PropertyTreeNode, parentField = this.rootField): void {
27    const protoType = parentField.tamperedMessageType;
28
29    if (protoType === undefined) {
30      return;
31    }
32
33    let field = parentField;
34    if (field.name !== value.name) {
35      field = protoType.fields[value.name] ?? parentField;
36    }
37
38    if (value.getAllChildren().length > 0) {
39      value.getAllChildren().forEach((value) => {
40        this.apply(value, field);
41      });
42    } else {
43      if (typeof value.getValue() === 'number' && value.getValue() !== -1) {
44        const translation = this.translateIntDefToStringIfNeeded(
45          value.getValue(),
46          field,
47        );
48        if (typeof translation === 'string') {
49          value.setFormatter(new FixedStringFormatter(translation));
50        }
51      }
52    }
53  }
54
55  private translateIntDefToStringIfNeeded(
56    value: number,
57    field: TamperedProtoField,
58  ): string | number {
59    const typeDefSpec = this.getTypeDefSpecFromField(field);
60
61    if (typeDefSpec) {
62      return this.getIntFlagsAsStrings(value, typeDefSpec);
63    } else {
64      const propertyPath = `${field.parent?.name}.${field.name}`;
65      if (this.intDefColumn[propertyPath]) {
66        return this.getIntFlagsAsStrings(
67          value,
68          this.intDefColumn[propertyPath] as string,
69        );
70      }
71    }
72
73    return value;
74  }
75
76  private getTypeDefSpecFromField(
77    field: TamperedProtoField,
78  ): string | undefined {
79    if (field.options === undefined) {
80      return undefined;
81    } else if (field.options['(.android.typedef)'] !== undefined) {
82      return field.options['(.android.typedef)'];
83    } else if (field.options['(.perfetto.protos.typedef)'] !== undefined) {
84      return field.options['(.perfetto.protos.typedef)'];
85    }
86    return undefined;
87  }
88
89  private getIntFlagsAsStrings(
90    intFlags: number,
91    annotationType: string,
92  ): string {
93    let flags = '';
94
95    const mapping =
96      intDefMapping[annotationType as keyof typeof intDefMapping].values;
97
98    const knownFlagValues = Object.keys(mapping)
99      .reverse()
100      .map((x) => Math.floor(Number(x)));
101
102    if (knownFlagValues.length === 0) {
103      console.warn('No mapping for type', annotationType);
104      return intFlags + '';
105    }
106
107    // Will only contain bits that have not been associated with a flag.
108    const parsedIntFlags = Math.floor(Number(intFlags));
109    let leftOver = parsedIntFlags;
110
111    for (const flagValue of knownFlagValues) {
112      if (
113        (leftOver & flagValue && (intFlags & flagValue) === flagValue) ||
114        (parsedIntFlags === 0 && flagValue === 0)
115      ) {
116        if (flags.length > 0) flags += ' | ';
117        flags += mapping[flagValue as keyof typeof mapping];
118
119        leftOver = leftOver & ~flagValue;
120      }
121    }
122
123    if (flags.length === 0) {
124      return `${intFlags}`;
125    }
126
127    if (leftOver) {
128      // If 0 is a valid flag value that isn't in the intDefMapping it will be ignored
129      flags += leftOver;
130    }
131
132    return flags;
133  }
134
135  private readonly intDefColumn: {[key: string]: string} = {
136    'WindowLayoutParams.type':
137      'android.view.WindowManager.LayoutParams.WindowType',
138    'WindowLayoutParams.flags': 'android.view.WindowManager.LayoutParams.Flags',
139    'WindowLayoutParams.privateFlags':
140      'android.view.WindowManager.LayoutParams.PrivateFlags',
141    'WindowLayoutParams.gravity': 'android.view.Gravity.GravityFlags',
142    'WindowLayoutParams.softInputMode':
143      'android.view.WindowManager.LayoutParams.WindowType',
144    'WindowLayoutParams.systemUiVisibilityFlags':
145      'android.view.WindowManager.LayoutParams.SystemUiVisibilityFlags',
146    'WindowLayoutParams.subtreeSystemUiVisibilityFlags':
147      'android.view.WindowManager.LayoutParams.SystemUiVisibilityFlags',
148    'WindowLayoutParams.behavior':
149      'android.view.WindowInsetsController.Behavior',
150    'WindowLayoutParams.fitInsetsSides':
151      'android.view.WindowInsets.Side.InsetsSide',
152    'InputWindowInfoProto.layoutParamsFlags':
153      'android.view.WindowManager.LayoutParams.Flags',
154    'InputWindowInfoProto.inputConfig':
155      'android.view.InputWindowHandle.InputConfigFlags',
156    'Configuration.windowingMode':
157      'android.app.WindowConfiguration.WindowingMode',
158    'WindowConfiguration.windowingMode':
159      'android.app.WindowConfiguration.WindowingMode',
160    'Configuration.orientation':
161      'android.content.pm.ActivityInfo.ScreenOrientation',
162    'WindowConfiguration.orientation':
163      'android.content.pm.ActivityInfo.ScreenOrientation',
164    'WindowState.orientation':
165      'android.content.pm.ActivityInfo.ScreenOrientation',
166    'InsetsSourceControlProto.typeNumber':
167      'android.view.WindowInsets.Type.InsetsType',
168    'InsetsSourceConsumerProto.typeNumber':
169      'android.view.WindowInsets.Type.InsetsType',
170  };
171}
172