1#!/usr/bin/python 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 tempfile 23import os 24import logging 25import subprocess 26import xml.etree.ElementTree as ET 27import xml.etree.ElementInclude as EI 28import xml.dom.minidom as MINIDOM 29from collections import OrderedDict 30 31# 32# Helper script that helps to feed at build time the XML criterion types file used by 33# the engineconfigurable to start the parameter-framework. 34# It prevents to fill them manually and avoid divergences with android. 35# 36# The Device Types criterion types are fed from audio-base.h file with the option 37# --androidaudiobaseheader <path/to/android/audio/base/file/audio-base.h> 38# 39# The Device Addresses criterion types are fed from the audio policy configuration file 40# in order to discover all the devices for which the address matter. 41# --audiopolicyconfigurationfile <path/to/audio_policy_configuration.xml> 42# 43# The reference file of criterion types must also be set as an input of the script: 44# --criteriontypes <path/to/criterion/file/audio_criterion_types.xml.in> 45# 46# At last, the output of the script shall be set also: 47# --outputfile <path/to/out/vendor/etc/audio_criterion_types.xml> 48# 49 50def parseArgs(): 51 argparser = argparse.ArgumentParser(description="Parameter-Framework XML \ 52 audio criterion type file generator.\n\ 53 Exit with the number of (recoverable or not) error that occured.") 54 argparser.add_argument('--androidaudiobaseheader', 55 help="Android Audio Base C header file, Mandatory.", 56 metavar="ANDROID_AUDIO_BASE_HEADER", 57 type=argparse.FileType('r'), 58 required=True) 59 argparser.add_argument('--audiopolicyconfigurationfile', 60 help="Android Audio Policy Configuration file, Mandatory.", 61 metavar="(AUDIO_POLICY_CONFIGURATION_FILE)", 62 type=argparse.FileType('r'), 63 required=True) 64 argparser.add_argument('--criteriontypes', 65 help="Criterion types XML base file, in \ 66 '<criterion_types> \ 67 <criterion_type name="" type=<inclusive|exclusive> 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.encode('utf-8')) 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 oldWorkingDir = os.getcwd() 147 print "Current working directory %s" % oldWorkingDir 148 149 newDir = os.path.join(oldWorkingDir , audiopolicyconfigurationfile.name) 150 151 policy_in_tree = ET.parse(audiopolicyconfigurationfile) 152 os.chdir(os.path.dirname(os.path.normpath(newDir))) 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(oldWorkingDir) 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 all_criteria[criterion_name].keys(): 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