1#! /usr/bin/python3 2# 3# pylint: disable=line-too-long, missing-docstring, logging-format-interpolation, invalid-name 4 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17 18import argparse 19import re 20import sys 21import os 22import logging 23import xml.etree.ElementTree as ET 24from collections import OrderedDict 25import xml.dom.minidom as MINIDOM 26 27def parseArgs(): 28 argparser = argparse.ArgumentParser(description="Parameter-Framework XML \ 29 structure file generator.\n\ 30 Exit with the number of (recoverable or not) error that occured.") 31 argparser.add_argument('--androidaudiobaseheader', 32 help="Android Audio Base C header file, Mandatory.", 33 metavar="ANDROID_AUDIO_BASE_HEADER", 34 type=argparse.FileType('r'), 35 required=True) 36 argparser.add_argument('--commontypesstructure', 37 help="Structure XML base file. Mandatory.", 38 metavar="STRUCTURE_FILE_IN", 39 type=argparse.FileType('r'), 40 required=True) 41 argparser.add_argument('--outputfile', 42 help="Structure XML file. Mandatory.", 43 metavar="STRUCTURE_FILE_OUT", 44 type=argparse.FileType('w'), 45 required=True) 46 argparser.add_argument('--verbose', 47 action='store_true') 48 49 return argparser.parse_args() 50 51 52def findBitPos(decimal): 53 pos = 0 54 i = 1 55 while i < decimal: 56 i = i << 1 57 pos = pos + 1 58 if pos == 64: 59 return -1 60 61 # TODO: b/168065706. This is just to fix the build. That the problem of devices with 62 # multiple bits set must be addressed more generally in the configurable audio policy 63 # and parameter framework. 64 if i > decimal: 65 logging.info("Device:{} which has multiple bits set is skipped. b/168065706".format(decimal)) 66 return -2 67 return pos 68 69def generateXmlStructureFile(componentTypeDict, structureTypesFile, outputFile): 70 71 logging.info("Importing structureTypesFile {}".format(structureTypesFile)) 72 component_types_in_tree = ET.parse(structureTypesFile) 73 74 component_types_root = component_types_in_tree.getroot() 75 76 for component_types_name, values_dict in componentTypeDict.items(): 77 for component_type in component_types_root.findall('ComponentType'): 78 if component_type.get('Name') == component_types_name: 79 bitparameters_node = component_type.find("BitParameterBlock") 80 if bitparameters_node is not None: 81 ordered_values = OrderedDict(sorted(values_dict.items(), key=lambda x: x[1])) 82 for key, value in ordered_values.items(): 83 pos = findBitPos(value) 84 if pos >= 0: 85 value_node = ET.SubElement(bitparameters_node, "BitParameter") 86 value_node.set('Name', key) 87 value_node.set('Size', "1") 88 value_node.set('Pos', str(pos)) 89 90 enum_parameter_node = component_type.find("EnumParameter") 91 if enum_parameter_node is not None: 92 ordered_values = OrderedDict(sorted(values_dict.items(), key=lambda x: x[1])) 93 for key, value in ordered_values.items(): 94 value_node = ET.SubElement(enum_parameter_node, "ValuePair") 95 value_node.set('Literal', key) 96 value_node.set('Numerical', str(value)) 97 98 xmlstr = ET.tostring(component_types_root, encoding='utf8', method='xml') 99 reparsed = MINIDOM.parseString(xmlstr) 100 prettyXmlStr = reparsed.toprettyxml(indent=" ", newl='\n') 101 prettyXmlStr = os.linesep.join([s for s in prettyXmlStr.splitlines() if s.strip()]) 102 outputFile.write(prettyXmlStr) 103 104 105def capitalizeLine(line): 106 return ' '.join((w.capitalize() for w in line.split(' '))) 107 108def parseAndroidAudioFile(androidaudiobaseheaderFile): 109 # 110 # Adaptation table between Android Enumeration prefix and Audio PFW Criterion type names 111 # 112 component_type_mapping_table = { 113 'AUDIO_STREAM' : "VolumeProfileType", 114 'AUDIO_DEVICE_OUT' : "OutputDevicesMask", 115 'AUDIO_DEVICE_IN' : "InputDevicesMask"} 116 117 all_component_types = { 118 'VolumeProfileType' : {}, 119 'OutputDevicesMask' : {}, 120 'InputDevicesMask' : {} 121 } 122 123 # 124 # _CNT, _MAX, _ALL and _NONE are prohibited values as ther are just helpers for enum users. 125 # 126 ignored_values = ['CNT', 'MAX', 'ALL', 'NONE'] 127 128 criteria_pattern = re.compile( 129 r"\s*V\((?P<type>(?:"+'|'.join(component_type_mapping_table.keys()) + "))_" \ 130 r"(?P<literal>(?!" + '|'.join(ignored_values) + ")\w*)\s*,\s*" \ 131 r"(?:AUDIO_DEVICE_BIT_IN \| )?(?P<values>(?:0[xX])[0-9a-fA-F]+|[0-9]+)") 132 133 logging.info("Checking Android Header file {}".format(androidaudiobaseheaderFile)) 134 135 multi_bit_output_device_shift = 32 136 multi_bit_input_device_shift = 32 137 138 for line_number, line in enumerate(androidaudiobaseheaderFile): 139 match = criteria_pattern.match(line) 140 if match: 141 logging.debug("The following line is VALID: {}:{}\n{}".format( 142 androidaudiobaseheaderFile.name, line_number, line)) 143 144 component_type_name = component_type_mapping_table[match.groupdict()['type']] 145 component_type_literal = match.groupdict()['literal'].lower() 146 147 component_type_numerical_value = match.groupdict()['values'] 148 149 # for AUDIO_DEVICE_IN: rename default to stub 150 if component_type_name == "InputDevicesMask": 151 component_type_numerical_value = str(int(component_type_numerical_value, 0)) 152 if component_type_literal == "default": 153 component_type_literal = "stub" 154 155 string_int = int(component_type_numerical_value, 0) 156 num_bits = bin(string_int).count("1") 157 if num_bits > 1: 158 logging.info("The value {} is for criterion {} binary rep {} has {} bits sets" 159 .format(component_type_numerical_value, component_type_name, bin(string_int), num_bits)) 160 string_int = 2**multi_bit_input_device_shift 161 logging.info("new val assigned is {} {}" .format(string_int, bin(string_int))) 162 multi_bit_input_device_shift += 1 163 component_type_numerical_value = str(string_int) 164 165 if component_type_name == "OutputDevicesMask": 166 if component_type_literal == "default": 167 component_type_literal = "stub" 168 169 string_int = int(component_type_numerical_value, 0) 170 num_bits = bin(string_int).count("1") 171 if num_bits > 1: 172 logging.info("The value {} is for criterion {} binary rep {} has {} bits sets" 173 .format(component_type_numerical_value, component_type_name, bin(string_int), num_bits)) 174 string_int = 2**multi_bit_output_device_shift 175 logging.info("new val assigned is {} {}" .format(string_int, bin(string_int))) 176 multi_bit_output_device_shift += 1 177 component_type_numerical_value = str(string_int) 178 179 # Remove duplicated numerical values 180 if int(component_type_numerical_value, 0) in all_component_types[component_type_name].values(): 181 logging.info("The value {}:{} is duplicated for criterion {}, KEEPING LATEST".format(component_type_numerical_value, component_type_literal, component_type_name)) 182 for key in list(all_component_types[component_type_name]): 183 if all_component_types[component_type_name][key] == int(component_type_numerical_value, 0): 184 del all_component_types[component_type_name][key] 185 186 all_component_types[component_type_name][component_type_literal] = int(component_type_numerical_value, 0) 187 188 logging.debug("type:{}, literal:{}, values:{}.".format(component_type_name, component_type_literal, component_type_numerical_value)) 189 190 if "stub" not in all_component_types["OutputDevicesMask"]: 191 all_component_types["OutputDevicesMask"]["stub"] = 0x40000000 192 logging.info("added stub output device mask") 193 if "stub" not in all_component_types["InputDevicesMask"]: 194 all_component_types["InputDevicesMask"]["stub"] = 0x40000000 195 logging.info("added stub input device mask") 196 197 # Transform input source in inclusive criterion 198 for component_types in all_component_types: 199 values = ','.join('{}:{}'.format(value, key) for key, value in all_component_types[component_types].items()) 200 logging.info("{}: <{}>".format(component_types, values)) 201 202 return all_component_types 203 204 205def main(): 206 logging.root.setLevel(logging.INFO) 207 args = parseArgs() 208 route_criteria = 0 209 210 all_component_types = parseAndroidAudioFile(args.androidaudiobaseheader) 211 212 generateXmlStructureFile(all_component_types, args.commontypesstructure, args.outputfile) 213 214# If this file is directly executed 215if __name__ == "__main__": 216 sys.exit(main()) 217