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('--audiopolicyconfigurationfile', 59 help="Android Audio Policy Configuration file, Mandatory.", 60 metavar="(AUDIO_POLICY_CONFIGURATION_FILE)", 61 type=argparse.FileType('r'), 62 required=True) 63 argparser.add_argument('--criteriontypes', 64 help="Criterion types XML base file, in \ 65 '<criterion_types> \ 66 <criterion_type name="" type=<inclusive|exclusive> \ 67 values=<value1,value2,...>/>' \ 68 format. Mandatory.", 69 metavar="CRITERION_TYPE_FILE", 70 type=argparse.FileType('r'), 71 required=True) 72 argparser.add_argument('--outputfile', 73 help="Criterion types outputfile file. Mandatory.", 74 metavar="CRITERION_TYPE_OUTPUT_FILE", 75 type=argparse.FileType('w'), 76 required=True) 77 argparser.add_argument('--verbose', 78 action='store_true') 79 80 return argparser.parse_args() 81 82 83def generateXmlCriterionTypesFile(criterionTypes, addressCriteria, criterionTypesFile, outputFile): 84 85 logging.info("Importing criterionTypesFile {}".format(criterionTypesFile)) 86 criterion_types_in_tree = ET.parse(criterionTypesFile) 87 88 criterion_types_root = criterion_types_in_tree.getroot() 89 90 for criterion_name, values_dict in criterionTypes.items(): 91 for criterion_type in criterion_types_root.findall('criterion_type'): 92 if criterion_type.get('name') == criterion_name: 93 values_node = ET.SubElement(criterion_type, "values") 94 ordered_values = OrderedDict(sorted(values_dict.items(), key=lambda x: x[1])) 95 for key, value in ordered_values.items(): 96 value_node = ET.SubElement(values_node, "value") 97 value_node.set('numerical', str(value)) 98 value_node.set('literal', key) 99 100 if addressCriteria: 101 for criterion_name, values_list in addressCriteria.items(): 102 for criterion_type in criterion_types_root.findall('criterion_type'): 103 if criterion_type.get('name') == criterion_name: 104 index = 0 105 existing_values_node = criterion_type.find("values") 106 if existing_values_node is not None: 107 for existing_value in existing_values_node.findall('value'): 108 if existing_value.get('numerical') == str(1 << index): 109 index += 1 110 values_node = existing_values_node 111 else: 112 values_node = ET.SubElement(criterion_type, "values") 113 114 for value in values_list: 115 value_node = ET.SubElement(values_node, "value", literal=value) 116 value_node.set('numerical', str(1 << index)) 117 index += 1 118 119 xmlstr = ET.tostring(criterion_types_root, encoding='utf8', method='xml') 120 reparsed = MINIDOM.parseString(xmlstr) 121 prettyXmlStr = reparsed.toprettyxml(newl='\r\n') 122 prettyXmlStr = os.linesep.join([s for s in prettyXmlStr.splitlines() if s.strip()]) 123 outputFile.write(prettyXmlStr) 124 125def capitalizeLine(line): 126 return ' '.join((w.capitalize() for w in line.split(' '))) 127 128 129# 130# Parse the audio policy configuration file and output a dictionary of device criteria addresses 131# 132def parseAndroidAudioPolicyConfigurationFile(audiopolicyconfigurationfile): 133 134 logging.info("Checking Audio Policy Configuration file {}".format(audiopolicyconfigurationfile)) 135 # 136 # extract all devices addresses from audio policy configuration file 137 # 138 address_criteria_mapping_table = { 139 'sink' : "OutputDevicesAddressesType", 140 'source' : "InputDevicesAddressesType"} 141 142 address_criteria = { 143 'OutputDevicesAddressesType' : [], 144 'InputDevicesAddressesType' : []} 145 146 old_working_dir = os.getcwd() 147 print("Current working directory %s" % old_working_dir) 148 149 new_dir = os.path.join(old_working_dir, audiopolicyconfigurationfile.name) 150 151 policy_in_tree = ET.parse(audiopolicyconfigurationfile) 152 os.chdir(os.path.dirname(os.path.normpath(new_dir))) 153 154 print("new working directory %s" % os.getcwd()) 155 156 policy_root = policy_in_tree.getroot() 157 EI.include(policy_root) 158 159 os.chdir(old_working_dir) 160 161 for device in policy_root.iter('devicePort'): 162 for key in address_criteria_mapping_table.keys(): 163 if device.get('role') == key and device.get('address'): 164 logging.info("{}: <{}>".format(key, device.get('address'))) 165 address_criteria[address_criteria_mapping_table[key]].append(device.get('address')) 166 167 for criteria in address_criteria: 168 values = ','.join(address_criteria[criteria]) 169 logging.info("{}: <{}>".format(criteria, values)) 170 171 return address_criteria 172 173# 174# Parse the audio-base.h file and output a dictionary of android dependent criterion types: 175# -Android Mode 176# -Output devices type 177# -Input devices type 178# 179def parseAndroidAudioFile(androidaudiobaseheaderFile): 180 # 181 # Adaptation table between Android Enumeration prefix and Audio PFW Criterion type names 182 # 183 criterion_mapping_table = { 184 'AUDIO_MODE' : "AndroidModeType", 185 'AUDIO_DEVICE_OUT' : "OutputDevicesMaskType", 186 'AUDIO_DEVICE_IN' : "InputDevicesMaskType"} 187 188 all_criteria = { 189 'AndroidModeType' : {}, 190 'OutputDevicesMaskType' : {}, 191 'InputDevicesMaskType' : {}} 192 193 # 194 # _CNT, _MAX, _ALL and _NONE are prohibited values as ther are just helpers for enum users. 195 # 196 ignored_values = ['CNT', 'MAX', 'ALL', 'NONE'] 197 198 criteria_pattern = re.compile( 199 r"\s*(?P<type>(?:"+'|'.join(criterion_mapping_table.keys()) + "))_" \ 200 r"(?P<literal>(?!" + '|'.join(ignored_values) + ")\w*)\s*=\s*" \ 201 r"(?P<values>(?:0[xX])?[0-9a-fA-F]+)") 202 203 logging.info("Checking Android Header file {}".format(androidaudiobaseheaderFile)) 204 205 for line_number, line in enumerate(androidaudiobaseheaderFile): 206 match = criteria_pattern.match(line) 207 if match: 208 logging.debug("The following line is VALID: {}:{}\n{}".format( 209 androidaudiobaseheaderFile.name, line_number, line)) 210 211 criterion_name = criterion_mapping_table[match.groupdict()['type']] 212 literal = ''.join((w.capitalize() for w in match.groupdict()['literal'].split('_'))) 213 numerical_value = match.groupdict()['values'] 214 215 # for AUDIO_DEVICE_IN: need to remove sign bit 216 if criterion_name == "InputDevicesMaskType": 217 numerical_value = str(int(numerical_value, 0) & ~2147483648) 218 219 # Remove duplicated numerical values 220 if int(numerical_value, 0) in all_criteria[criterion_name].values(): 221 logging.info("criterion {} duplicated values:".format(criterion_name)) 222 logging.info("{}:{}".format(numerical_value, literal)) 223 logging.info("KEEPING LATEST") 224 for key in list(all_criteria[criterion_name]): 225 if all_criteria[criterion_name][key] == int(numerical_value, 0): 226 del all_criteria[criterion_name][key] 227 228 all_criteria[criterion_name][literal] = int(numerical_value, 0) 229 230 logging.debug("type:{},".format(criterion_name)) 231 logging.debug("iteral:{},".format(literal)) 232 logging.debug("values:{}.".format(numerical_value)) 233 234 return all_criteria 235 236 237def main(): 238 logging.root.setLevel(logging.INFO) 239 args = parseArgs() 240 241 all_criteria = parseAndroidAudioFile(args.androidaudiobaseheader) 242 243 address_criteria = parseAndroidAudioPolicyConfigurationFile(args.audiopolicyconfigurationfile) 244 245 criterion_types = args.criteriontypes 246 247 generateXmlCriterionTypesFile(all_criteria, address_criteria, criterion_types, args.outputfile) 248 249# If this file is directly executed 250if __name__ == "__main__": 251 sys.exit(main()) 252