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 occurred.") 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('literal', key) 106 107 if criterion_type.get('name') == "OutputDevicesMaskType": 108 value_node.set('android_type', output_devices_type_value[key]) 109 if criterion_type.get('name') == "InputDevicesMaskType": 110 value_node.set('android_type', input_devices_type_value[key]) 111 112 if addressCriteria: 113 for criterion_name, values_list in addressCriteria.items(): 114 for criterion_type in criterion_types_root.findall('criterion_type'): 115 if criterion_type.get('name') == criterion_name: 116 existing_values_node = criterion_type.find("values") 117 if existing_values_node is not None: 118 values_node = existing_values_node 119 else: 120 values_node = ET.SubElement(criterion_type, "values") 121 122 for value in values_list: 123 value_node = ET.SubElement(values_node, "value", literal=value) 124 125 xmlstr = ET.tostring(criterion_types_root, encoding='utf8', method='xml') 126 reparsed = MINIDOM.parseString(xmlstr) 127 prettyXmlStr = reparsed.toprettyxml(newl='\r\n') 128 prettyXmlStr = os.linesep.join([s for s in prettyXmlStr.splitlines() if s.strip()]) 129 outputFile.write(prettyXmlStr) 130 131def capitalizeLine(line): 132 return ' '.join((w.capitalize() for w in line.split(' '))) 133 134 135# 136# Parse the audio policy configuration file and output a dictionary of device criteria addresses 137# 138def parseAndroidAudioPolicyConfigurationFile(audiopolicyconfigurationfile): 139 140 logging.info("Checking Audio Policy Configuration file {}".format(audiopolicyconfigurationfile)) 141 # 142 # extract all devices addresses from audio policy configuration file 143 # 144 address_criteria_mapping_table = { 145 'sink' : "OutputDevicesAddressesType", 146 'source' : "InputDevicesAddressesType"} 147 148 address_criteria = { 149 'OutputDevicesAddressesType' : [], 150 'InputDevicesAddressesType' : []} 151 152 old_working_dir = os.getcwd() 153 print("Current working directory %s" % old_working_dir) 154 155 new_dir = os.path.join(old_working_dir, audiopolicyconfigurationfile.name) 156 157 policy_in_tree = ET.parse(audiopolicyconfigurationfile) 158 os.chdir(os.path.dirname(os.path.normpath(new_dir))) 159 160 print("new working directory %s" % os.getcwd()) 161 162 policy_root = policy_in_tree.getroot() 163 EI.include(policy_root) 164 165 os.chdir(old_working_dir) 166 167 for device in policy_root.iter('devicePort'): 168 for key in address_criteria_mapping_table.keys(): 169 if device.get('role') == key and device.get('address'): 170 logging.info("{}: <{}>".format(key, device.get('address'))) 171 address_criteria[address_criteria_mapping_table[key]].append(device.get('address')) 172 173 for criteria in address_criteria: 174 values = ','.join(address_criteria[criteria]) 175 logging.info("{}: <{}>".format(criteria, values)) 176 177 return address_criteria 178 179# 180# Parse the audio-base.h file and output a dictionary of android dependent criterion types: 181# -Android Mode 182# -Output devices type 183# -Input devices type 184# 185def parseAndroidAudioFile(androidaudiobaseheaderFile, androidaudiocommonbaseheaderFile): 186 # 187 # Adaptation table between Android Enumeration prefix and Audio PFW Criterion type names 188 # 189 criterion_mapping_table = { 190 'HAL_AUDIO_MODE' : "AndroidModeType", 191 'AUDIO_DEVICE_OUT' : "OutputDevicesMaskType", 192 'AUDIO_DEVICE_IN' : "InputDevicesMaskType"} 193 194 all_criteria = { 195 'AndroidModeType' : {}, 196 'OutputDevicesMaskType' : {}, 197 'InputDevicesMaskType' : {}} 198 199 # 200 # _CNT, _MAX, _ALL and _NONE are prohibited values as ther are just helpers for enum users. 201 # 202 ignored_values = ['CNT', 'MAX', 'ALL', 'NONE'] 203 204 multi_bit_outputdevice_shift = 32 205 multi_bit_inputdevice_shift = 32 206 207 criteria_pattern = re.compile( 208 r"\s*V\((?P<type>(?:"+'|'.join(criterion_mapping_table.keys()) + "))_" \ 209 r"(?P<literal>(?!" + '|'.join(ignored_values) + ")\w*)\s*,\s*" \ 210 r"(?:AUDIO_DEVICE_BIT_IN \| )?(?P<values>(?:0[xX])?[0-9a-fA-F]+|[0-9]+)") 211 212 logging.info("Checking Android Header file {}".format(androidaudiobaseheaderFile)) 213 214 for line_number, line in enumerate(androidaudiobaseheaderFile): 215 match = criteria_pattern.match(line) 216 if match: 217 logging.debug("The following line is VALID: {}:{}\n{}".format( 218 androidaudiobaseheaderFile.name, line_number, line)) 219 220 criterion_name = criterion_mapping_table[match.groupdict()['type']] 221 criterion_literal = ''.join(match.groupdict()['literal']) 222 criterion_numerical_value = match.groupdict()['values'] 223 224 if criterion_name == "InputDevicesMaskType": 225 # Remove ambient and in_communication since they were deprecated 226 logging.info("Remove deprecated device {}".format(criterion_literal)) 227 if criterion_literal == "AMBIENT" or criterion_literal == "COMMUNICATION": 228 logging.info("Remove deprecated device {}".format(criterion_literal)) 229 continue 230 # for AUDIO_DEVICE_IN: rename default to stub 231 elif criterion_literal == "DEFAULT": 232 criterion_numerical_value = str(int("0x40000000", 0)) 233 input_devices_type_value[criterion_literal] = "0xC0000000" 234 else: 235 try: 236 string_int = int(criterion_numerical_value, 0) 237 # Append AUDIO_DEVICE_IN for android type tag 238 input_devices_type_value[criterion_literal] = hex(string_int | 2147483648) 239 240 num_bits = bin(string_int).count("1") 241 if num_bits > 1: 242 logging.info("The value {}:{} is for criterion {} binary rep {} has {} bits sets" 243 .format(criterion_numerical_value, criterion_literal, criterion_name, bin(string_int), num_bits)) 244 string_int = 2**multi_bit_inputdevice_shift 245 logging.info("new val assigned is {} {}" .format(string_int, bin(string_int))) 246 multi_bit_inputdevice_shift += 1 247 criterion_numerical_value = str(string_int) 248 249 except ValueError: 250 # Handle the exception 251 logging.info("value {}:{} for criterion {} is not a number, ignoring" 252 .format(criterion_numerical_value, criterion_literal, criterion_name)) 253 continue 254 255 if criterion_name == "OutputDevicesMaskType": 256 if criterion_literal == "DEFAULT": 257 criterion_numerical_value = str(int("0x40000000", 0)) 258 output_devices_type_value[criterion_literal] = "0x40000000" 259 else: 260 try: 261 string_int = int(criterion_numerical_value, 0) 262 output_devices_type_value[criterion_literal] = criterion_numerical_value 263 264 num_bits = bin(string_int).count("1") 265 if num_bits > 1: 266 logging.info("The value {}:{} is for criterion {} binary rep {} has {} bits sets" 267 .format(criterion_numerical_value, criterion_literal, criterion_name, bin(string_int), num_bits)) 268 string_int = 2**multi_bit_outputdevice_shift 269 logging.info("new val assigned is {} {}" .format(string_int, bin(string_int))) 270 multi_bit_outputdevice_shift += 1 271 criterion_numerical_value = str(string_int) 272 273 except ValueError: 274 # Handle the exception 275 logging.info("The value {}:{} is for criterion {} is not a number, ignoring" 276 .format(criterion_numerical_value, criterion_literal, criterion_name)) 277 continue 278 279 try: 280 string_int = int(criterion_numerical_value, 0) 281 282 except ValueError: 283 # Handle the exception 284 logging.info("The value {}:{} is for criterion {} is not a number, ignoring" 285 .format(criterion_numerical_value, criterion_literal, criterion_name)) 286 continue 287 288 # Remove duplicated numerical values 289 if int(criterion_numerical_value, 0) in all_criteria[criterion_name].values(): 290 logging.info("criterion {} duplicated values:".format(criterion_name)) 291 logging.info("{}:{}".format(criterion_numerical_value, criterion_literal)) 292 logging.info("KEEPING LATEST") 293 for key in list(all_criteria[criterion_name]): 294 if all_criteria[criterion_name][key] == int(criterion_numerical_value, 0): 295 del all_criteria[criterion_name][key] 296 297 all_criteria[criterion_name][criterion_literal] = int(criterion_numerical_value, 0) 298 299 logging.debug("type:{},".format(criterion_name)) 300 logging.debug("iteral:{},".format(criterion_literal)) 301 logging.debug("values:{}.".format(criterion_numerical_value)) 302 303 logging.info("Checking Android Common Header file {}".format(androidaudiocommonbaseheaderFile)) 304 305 criteria_pattern = re.compile( 306 r"\s*(?P<type>(?:"+'|'.join(criterion_mapping_table.keys()) + "))_" \ 307 r"(?P<literal>(?!" + '|'.join(ignored_values) + ")\w*)\s*=\s*" \ 308 r"(?:AUDIO_DEVICE_BIT_IN \| )?(?P<values>(?:0[xX])?[0-9a-fA-F]+|[0-9]+)") 309 310 for line_number, line in enumerate(androidaudiocommonbaseheaderFile): 311 match = criteria_pattern.match(line) 312 if match: 313 logging.debug("The following line is VALID: {}:{}\n{}".format( 314 androidaudiocommonbaseheaderFile.name, line_number, line)) 315 316 criterion_name = criterion_mapping_table[match.groupdict()['type']] 317 criterion_literal = ''.join(match.groupdict()['literal']) 318 criterion_numerical_value = match.groupdict()['values'] 319 320 try: 321 string_int = int(criterion_numerical_value, 0) 322 except ValueError: 323 # Handle the exception 324 logging.info("The value {}:{} is for criterion {} is not a number, ignoring" 325 .format(criterion_numerical_value, criterion_literal, criterion_name)) 326 continue 327 328 # Remove duplicated numerical values 329 if int(criterion_numerical_value, 0) in all_criteria[criterion_name].values(): 330 logging.info("criterion {} duplicated values:".format(criterion_name)) 331 logging.info("{}:{}".format(criterion_numerical_value, criterion_literal)) 332 logging.info("KEEPING LATEST") 333 for key in list(all_criteria[criterion_name]): 334 if all_criteria[criterion_name][key] == int(criterion_numerical_value, 0): 335 del all_criteria[criterion_name][key] 336 337 all_criteria[criterion_name][criterion_literal] = int(criterion_numerical_value, 0) 338 339 logging.debug("type:{},".format(criterion_name)) 340 logging.debug("iteral:{},".format(criterion_literal)) 341 logging.debug("values:{}.".format(criterion_numerical_value)) 342 343 return all_criteria 344 345 346def main(): 347 logging.root.setLevel(logging.INFO) 348 args = parseArgs() 349 350 all_criteria = parseAndroidAudioFile(args.androidaudiobaseheader, 351 args.androidaudiocommonbaseheader) 352 353 address_criteria = parseAndroidAudioPolicyConfigurationFile(args.audiopolicyconfigurationfile) 354 355 criterion_types = args.criteriontypes 356 357 generateXmlCriterionTypesFile(all_criteria, address_criteria, criterion_types, args.outputfile) 358 359# If this file is directly executed 360if __name__ == "__main__": 361 sys.exit(main()) 362