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