1#!/usr/bin/python3 2 3# 4# Copyright 2018, The Android Open Source Project 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# 18 19import argparse 20import re 21import sys 22import os 23import logging 24import xml.etree.ElementTree as ET 25import xml.etree.ElementInclude as EI 26import xml.dom.minidom as MINIDOM 27from collections import OrderedDict 28 29# 30# Helper script that helps to feed at build time the XML criterion types file used by 31# the engineconfigurable to start the parameter-framework. 32# It prevents to fill them manually and avoid divergences with android. 33# 34# The Device Types criterion types are fed from audio-base.h file with the option 35# --androidaudiobaseheader <path/to/android/audio/base/file/audio-base.h> 36# 37# The Device Addresses criterion types are fed from the audio policy configuration file 38# in order to discover all the devices for which the address matter. 39# --audiopolicyconfigurationfile <path/to/audio_policy_configuration.xml> 40# 41# The reference file of criterion types must also be set as an input of the script: 42# --criteriontypes <path/to/criterion/file/audio_criterion_types.xml.in> 43# 44# At last, the output of the script shall be set also: 45# --outputfile <path/to/out/vendor/etc/audio_criterion_types.xml> 46# 47 48def parseArgs(): 49 argparser = argparse.ArgumentParser(description="Parameter-Framework XML \ 50 audio criterion type file generator.\n\ 51 Exit with the number of (recoverable or not) \ 52 error that occured.") 53 argparser.add_argument('--androidaudiobaseheader', 54 help="Android Audio Base C header file, Mandatory.", 55 metavar="ANDROID_AUDIO_BASE_HEADER", 56 type=argparse.FileType('r'), 57 required=True) 58 argparser.add_argument('--androidaudiocommonbaseheader', 59 help="Android Audio CommonBase C header file, Mandatory.", 60 metavar="ANDROID_AUDIO_COMMON_BASE_HEADER", 61 type=argparse.FileType('r'), 62 required=True) 63 argparser.add_argument('--audiopolicyconfigurationfile', 64 help="Android Audio Policy Configuration file, Mandatory.", 65 metavar="(AUDIO_POLICY_CONFIGURATION_FILE)", 66 type=argparse.FileType('r'), 67 required=True) 68 argparser.add_argument('--criteriontypes', 69 help="Criterion types XML base file, in \ 70 '<criterion_types> \ 71 <criterion_type name="" type=<inclusive|exclusive> \ 72 values=<value1,value2,...>/>' \ 73 format. Mandatory.", 74 metavar="CRITERION_TYPE_FILE", 75 type=argparse.FileType('r'), 76 required=True) 77 argparser.add_argument('--outputfile', 78 help="Criterion types outputfile file. Mandatory.", 79 metavar="CRITERION_TYPE_OUTPUT_FILE", 80 type=argparse.FileType('w'), 81 required=True) 82 argparser.add_argument('--verbose', 83 action='store_true') 84 85 return argparser.parse_args() 86 87 88output_devices_type_value = {} 89input_devices_type_value = {} 90 91def generateXmlCriterionTypesFile(criterionTypes, addressCriteria, criterionTypesFile, outputFile): 92 93 logging.info("Importing criterionTypesFile {}".format(criterionTypesFile)) 94 criterion_types_in_tree = ET.parse(criterionTypesFile) 95 96 criterion_types_root = criterion_types_in_tree.getroot() 97 98 for criterion_name, values_dict in criterionTypes.items(): 99 for criterion_type in criterion_types_root.findall('criterion_type'): 100 if criterion_type.get('name') == criterion_name: 101 values_node = ET.SubElement(criterion_type, "values") 102 ordered_values = OrderedDict(sorted(values_dict.items(), key=lambda x: x[1])) 103 for key, value in ordered_values.items(): 104 value_node = ET.SubElement(values_node, "value") 105 value_node.set('numerical', str(value)) 106 value_node.set('literal', key) 107 108 if criterion_type.get('name') == "OutputDevicesMaskType": 109 value_node.set('android_type', output_devices_type_value[key]) 110 if criterion_type.get('name') == "InputDevicesMaskType": 111 value_node.set('android_type', input_devices_type_value[key]) 112 113 if addressCriteria: 114 for criterion_name, values_list in addressCriteria.items(): 115 for criterion_type in criterion_types_root.findall('criterion_type'): 116 if criterion_type.get('name') == criterion_name: 117 index = 0 118 existing_values_node = criterion_type.find("values") 119 if existing_values_node is not None: 120 for existing_value in existing_values_node.findall('value'): 121 if existing_value.get('numerical') == str(1 << index): 122 index += 1 123 values_node = existing_values_node 124 else: 125 values_node = ET.SubElement(criterion_type, "values") 126 127 for value in values_list: 128 value_node = ET.SubElement(values_node, "value", literal=value) 129 value_node.set('numerical', str(1 << index)) 130 index += 1 131 132 xmlstr = ET.tostring(criterion_types_root, encoding='utf8', method='xml') 133 reparsed = MINIDOM.parseString(xmlstr) 134 prettyXmlStr = reparsed.toprettyxml(newl='\r\n') 135 prettyXmlStr = os.linesep.join([s for s in prettyXmlStr.splitlines() if s.strip()]) 136 outputFile.write(prettyXmlStr) 137 138def capitalizeLine(line): 139 return ' '.join((w.capitalize() for w in line.split(' '))) 140 141 142# 143# Parse the audio policy configuration file and output a dictionary of device criteria addresses 144# 145def parseAndroidAudioPolicyConfigurationFile(audiopolicyconfigurationfile): 146 147 logging.info("Checking Audio Policy Configuration file {}".format(audiopolicyconfigurationfile)) 148 # 149 # extract all devices addresses from audio policy configuration file 150 # 151 address_criteria_mapping_table = { 152 'sink' : "OutputDevicesAddressesType", 153 'source' : "InputDevicesAddressesType"} 154 155 address_criteria = { 156 'OutputDevicesAddressesType' : [], 157 'InputDevicesAddressesType' : []} 158 159 old_working_dir = os.getcwd() 160 print("Current working directory %s" % old_working_dir) 161 162 new_dir = os.path.join(old_working_dir, audiopolicyconfigurationfile.name) 163 164 policy_in_tree = ET.parse(audiopolicyconfigurationfile) 165 os.chdir(os.path.dirname(os.path.normpath(new_dir))) 166 167 print("new working directory %s" % os.getcwd()) 168 169 policy_root = policy_in_tree.getroot() 170 EI.include(policy_root) 171 172 os.chdir(old_working_dir) 173 174 for device in policy_root.iter('devicePort'): 175 for key in address_criteria_mapping_table.keys(): 176 if device.get('role') == key and device.get('address'): 177 logging.info("{}: <{}>".format(key, device.get('address'))) 178 address_criteria[address_criteria_mapping_table[key]].append(device.get('address')) 179 180 for criteria in address_criteria: 181 values = ','.join(address_criteria[criteria]) 182 logging.info("{}: <{}>".format(criteria, values)) 183 184 return address_criteria 185 186# 187# Parse the audio-base.h file and output a dictionary of android dependent criterion types: 188# -Android Mode 189# -Output devices type 190# -Input devices type 191# 192def parseAndroidAudioFile(androidaudiobaseheaderFile, androidaudiocommonbaseheaderFile): 193 # 194 # Adaptation table between Android Enumeration prefix and Audio PFW Criterion type names 195 # 196 criterion_mapping_table = { 197 'HAL_AUDIO_MODE' : "AndroidModeType", 198 'AUDIO_DEVICE_OUT' : "OutputDevicesMaskType", 199 'AUDIO_DEVICE_IN' : "InputDevicesMaskType"} 200 201 all_criteria = { 202 'AndroidModeType' : {}, 203 'OutputDevicesMaskType' : {}, 204 'InputDevicesMaskType' : {}} 205 206 # 207 # _CNT, _MAX, _ALL and _NONE are prohibited values as ther are just helpers for enum users. 208 # 209 ignored_values = ['CNT', 'MAX', 'ALL', 'NONE'] 210 211 multi_bit_outputdevice_shift = 32 212 multi_bit_inputdevice_shift = 32 213 214 criteria_pattern = re.compile( 215 r"\s*V\((?P<type>(?:"+'|'.join(criterion_mapping_table.keys()) + "))_" \ 216 r"(?P<literal>(?!" + '|'.join(ignored_values) + ")\w*)\s*,\s*" \ 217 r"(?:AUDIO_DEVICE_BIT_IN \| )?(?P<values>(?:0[xX])?[0-9a-fA-F]+|[0-9]+)") 218 219 logging.info("Checking Android Header file {}".format(androidaudiobaseheaderFile)) 220 221 for line_number, line in enumerate(androidaudiobaseheaderFile): 222 match = criteria_pattern.match(line) 223 if match: 224 logging.debug("The following line is VALID: {}:{}\n{}".format( 225 androidaudiobaseheaderFile.name, line_number, line)) 226 227 criterion_name = criterion_mapping_table[match.groupdict()['type']] 228 criterion_literal = \ 229 ''.join((w.capitalize() for w in match.groupdict()['literal'].split('_'))) 230 criterion_numerical_value = match.groupdict()['values'] 231 232 # for AUDIO_DEVICE_IN: rename default to stub 233 if criterion_name == "InputDevicesMaskType": 234 if criterion_literal == "Default": 235 criterion_numerical_value = str(int("0x40000000", 0)) 236 input_devices_type_value[criterion_literal] = "0xC0000000" 237 else: 238 try: 239 string_int = int(criterion_numerical_value, 0) 240 # Append AUDIO_DEVICE_IN for android type tag 241 input_devices_type_value[criterion_literal] = hex(string_int | 2147483648) 242 243 num_bits = bin(string_int).count("1") 244 if num_bits > 1: 245 logging.info("The value {}:{} is for criterion {} binary rep {} has {} bits sets" 246 .format(criterion_numerical_value, criterion_literal, criterion_name, bin(string_int), num_bits)) 247 string_int = 2**multi_bit_inputdevice_shift 248 logging.info("new val assigned is {} {}" .format(string_int, bin(string_int))) 249 multi_bit_inputdevice_shift += 1 250 criterion_numerical_value = str(string_int) 251 252 except ValueError: 253 # Handle the exception 254 logging.info("value {}:{} for criterion {} is not a number, ignoring" 255 .format(criterion_numerical_value, criterion_literal, criterion_name)) 256 continue 257 258 if criterion_name == "OutputDevicesMaskType": 259 if criterion_literal == "Default": 260 criterion_numerical_value = str(int("0x40000000", 0)) 261 output_devices_type_value[criterion_literal] = "0x40000000" 262 else: 263 try: 264 string_int = int(criterion_numerical_value, 0) 265 output_devices_type_value[criterion_literal] = criterion_numerical_value 266 267 num_bits = bin(string_int).count("1") 268 if num_bits > 1: 269 logging.info("The value {}:{} is for criterion {} binary rep {} has {} bits sets" 270 .format(criterion_numerical_value, criterion_literal, criterion_name, bin(string_int), num_bits)) 271 string_int = 2**multi_bit_outputdevice_shift 272 logging.info("new val assigned is {} {}" .format(string_int, bin(string_int))) 273 multi_bit_outputdevice_shift += 1 274 criterion_numerical_value = str(string_int) 275 276 except ValueError: 277 # Handle the exception 278 logging.info("The value {}:{} is for criterion {} is not a number, ignoring" 279 .format(criterion_numerical_value, criterion_literal, criterion_name)) 280 continue 281 282 try: 283 string_int = int(criterion_numerical_value, 0) 284 285 except ValueError: 286 # Handle the exception 287 logging.info("The value {}:{} is for criterion {} is not a number, ignoring" 288 .format(criterion_numerical_value, criterion_literal, criterion_name)) 289 continue 290 291 # Remove duplicated numerical values 292 if int(criterion_numerical_value, 0) in all_criteria[criterion_name].values(): 293 logging.info("criterion {} duplicated values:".format(criterion_name)) 294 logging.info("{}:{}".format(criterion_numerical_value, criterion_literal)) 295 logging.info("KEEPING LATEST") 296 for key in list(all_criteria[criterion_name]): 297 if all_criteria[criterion_name][key] == int(criterion_numerical_value, 0): 298 del all_criteria[criterion_name][key] 299 300 all_criteria[criterion_name][criterion_literal] = int(criterion_numerical_value, 0) 301 302 logging.debug("type:{},".format(criterion_name)) 303 logging.debug("iteral:{},".format(criterion_literal)) 304 logging.debug("values:{}.".format(criterion_numerical_value)) 305 306 logging.info("Checking Android Common Header file {}".format(androidaudiocommonbaseheaderFile)) 307 308 criteria_pattern = re.compile( 309 r"\s*(?P<type>(?:"+'|'.join(criterion_mapping_table.keys()) + "))_" \ 310 r"(?P<literal>(?!" + '|'.join(ignored_values) + ")\w*)\s*=\s*" \ 311 r"(?:AUDIO_DEVICE_BIT_IN \| )?(?P<values>(?:0[xX])?[0-9a-fA-F]+|[0-9]+)") 312 313 for line_number, line in enumerate(androidaudiocommonbaseheaderFile): 314 match = criteria_pattern.match(line) 315 if match: 316 logging.debug("The following line is VALID: {}:{}\n{}".format( 317 androidaudiocommonbaseheaderFile.name, line_number, line)) 318 319 criterion_name = criterion_mapping_table[match.groupdict()['type']] 320 criterion_literal = \ 321 ''.join((w.capitalize() for w in match.groupdict()['literal'].split('_'))) 322 criterion_numerical_value = match.groupdict()['values'] 323 324 try: 325 string_int = int(criterion_numerical_value, 0) 326 except ValueError: 327 # Handle the exception 328 logging.info("The value {}:{} is for criterion {} is not a number, ignoring" 329 .format(criterion_numerical_value, criterion_literal, criterion_name)) 330 continue 331 332 # Remove duplicated numerical values 333 if int(criterion_numerical_value, 0) in all_criteria[criterion_name].values(): 334 logging.info("criterion {} duplicated values:".format(criterion_name)) 335 logging.info("{}:{}".format(criterion_numerical_value, criterion_literal)) 336 logging.info("KEEPING LATEST") 337 for key in list(all_criteria[criterion_name]): 338 if all_criteria[criterion_name][key] == int(criterion_numerical_value, 0): 339 del all_criteria[criterion_name][key] 340 341 all_criteria[criterion_name][criterion_literal] = int(criterion_numerical_value, 0) 342 343 logging.debug("type:{},".format(criterion_name)) 344 logging.debug("iteral:{},".format(criterion_literal)) 345 logging.debug("values:{}.".format(criterion_numerical_value)) 346 347 return all_criteria 348 349 350def main(): 351 logging.root.setLevel(logging.INFO) 352 args = parseArgs() 353 354 all_criteria = parseAndroidAudioFile(args.androidaudiobaseheader, 355 args.androidaudiocommonbaseheader) 356 357 address_criteria = parseAndroidAudioPolicyConfigurationFile(args.audiopolicyconfigurationfile) 358 359 criterion_types = args.criteriontypes 360 361 generateXmlCriterionTypesFile(all_criteria, address_criteria, criterion_types, args.outputfile) 362 363# If this file is directly executed 364if __name__ == "__main__": 365 sys.exit(main()) 366