blob: a63c8587a83d32c0f7cd51beb69280ac52980ff5 [file] [log] [blame]
#!/usr/bin/python
#
# Copyright 2018, The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import argparse
import re
import sys
import tempfile
import os
import logging
import subprocess
import xml.etree.ElementTree as ET
import xml.etree.ElementInclude as EI
import xml.dom.minidom as MINIDOM
from collections import OrderedDict
#
# Helper script that helps to feed at build time the XML criterion types file used by
# the engineconfigurable to start the parameter-framework.
# It prevents to fill them manually and avoid divergences with android.
#
# The Device Types criterion types are fed from audio-base.h file with the option
# --androidaudiobaseheader <path/to/android/audio/base/file/audio-base.h>
#
# The Device Addresses criterion types are fed from the audio policy configuration file
# in order to discover all the devices for which the address matter.
# --audiopolicyconfigurationfile <path/to/audio_policy_configuration.xml>
#
# The reference file of criterion types must also be set as an input of the script:
# --criteriontypes <path/to/criterion/file/audio_criterion_types.xml.in>
#
# At last, the output of the script shall be set also:
# --outputfile <path/to/out/vendor/etc/audio_criterion_types.xml>
#
def parseArgs():
argparser = argparse.ArgumentParser(description="Parameter-Framework XML \
audio criterion type file generator.\n\
Exit with the number of (recoverable or not) error that occured.")
argparser.add_argument('--androidaudiobaseheader',
help="Android Audio Base C header file, Mandatory.",
metavar="ANDROID_AUDIO_BASE_HEADER",
type=argparse.FileType('r'),
required=True)
argparser.add_argument('--audiopolicyconfigurationfile',
help="Android Audio Policy Configuration file, Mandatory.",
metavar="(AUDIO_POLICY_CONFIGURATION_FILE)",
type=argparse.FileType('r'),
required=True)
argparser.add_argument('--criteriontypes',
help="Criterion types XML base file, in \
'<criterion_types> \
<criterion_type name="" type=<inclusive|exclusive> values=<value1,value2,...>/>' \
format. Mandatory.",
metavar="CRITERION_TYPE_FILE",
type=argparse.FileType('r'),
required=True)
argparser.add_argument('--outputfile',
help="Criterion types outputfile file. Mandatory.",
metavar="CRITERION_TYPE_OUTPUT_FILE",
type=argparse.FileType('w'),
required=True)
argparser.add_argument('--verbose',
action='store_true')
return argparser.parse_args()
def generateXmlCriterionTypesFile(criterionTypes, addressCriteria, criterionTypesFile, outputFile):
logging.info("Importing criterionTypesFile {}".format(criterionTypesFile))
criterion_types_in_tree = ET.parse(criterionTypesFile)
criterion_types_root = criterion_types_in_tree.getroot()
for criterion_name, values_dict in criterionTypes.items():
for criterion_type in criterion_types_root.findall('criterion_type'):
if criterion_type.get('name') == criterion_name:
values_node = ET.SubElement(criterion_type, "values")
ordered_values = OrderedDict(sorted(values_dict.items(), key=lambda x: x[1]))
for key, value in ordered_values.items():
value_node = ET.SubElement(values_node, "value")
value_node.set('numerical', str(value))
value_node.set('literal', key)
if addressCriteria:
for criterion_name, values_list in addressCriteria.items():
for criterion_type in criterion_types_root.findall('criterion_type'):
if criterion_type.get('name') == criterion_name:
index = 0
existing_values_node = criterion_type.find("values")
if existing_values_node is not None:
for existing_value in existing_values_node.findall('value'):
if existing_value.get('numerical') == str(1 << index):
index += 1
values_node = existing_values_node
else:
values_node = ET.SubElement(criterion_type, "values")
for value in values_list:
value_node = ET.SubElement(values_node, "value", literal=value)
value_node.set('numerical', str(1 << index))
index += 1
xmlstr = ET.tostring(criterion_types_root, encoding='utf8', method='xml')
reparsed = MINIDOM.parseString(xmlstr)
prettyXmlStr = reparsed.toprettyxml(newl='\r\n')
prettyXmlStr = os.linesep.join([s for s in prettyXmlStr.splitlines() if s.strip()])
outputFile.write(prettyXmlStr.encode('utf-8'))
def capitalizeLine(line):
return ' '.join((w.capitalize() for w in line.split(' ')))
#
# Parse the audio policy configuration file and output a dictionary of device criteria addresses
#
def parseAndroidAudioPolicyConfigurationFile(audiopolicyconfigurationfile):
logging.info("Checking Audio Policy Configuration file {}".format(audiopolicyconfigurationfile))
#
# extract all devices addresses from audio policy configuration file
#
address_criteria_mapping_table = {
'sink' : "OutputDevicesAddressesType",
'source' : "InputDevicesAddressesType" }
address_criteria = {
'OutputDevicesAddressesType' : [],
'InputDevicesAddressesType' : [] }
oldWorkingDir = os.getcwd()
print "Current working directory %s" % oldWorkingDir
newDir = os.path.join(oldWorkingDir , audiopolicyconfigurationfile.name)
policy_in_tree = ET.parse(audiopolicyconfigurationfile)
os.chdir(os.path.dirname(os.path.normpath(newDir)))
print "new working directory %s" % os.getcwd()
policy_root = policy_in_tree.getroot()
EI.include(policy_root)
os.chdir(oldWorkingDir)
for device in policy_root.iter('devicePort'):
for key in address_criteria_mapping_table.keys():
if device.get('role') == key and device.get('address') :
logging.info("{}: <{}>".format(key, device.get('address')))
address_criteria[address_criteria_mapping_table[key]].append(device.get('address'))
for criteria in address_criteria:
values = ','.join(address_criteria[criteria])
logging.info("{}: <{}>".format(criteria, values))
return address_criteria
#
# Parse the audio-base.h file and output a dictionary of android dependent criterion types:
# -Android Mode
# -Output devices type
# -Input devices type
#
def parseAndroidAudioFile(androidaudiobaseheaderFile):
#
# Adaptation table between Android Enumeration prefix and Audio PFW Criterion type names
#
criterion_mapping_table = {
'AUDIO_MODE' : "AndroidModeType",
'AUDIO_DEVICE_OUT' : "OutputDevicesMaskType",
'AUDIO_DEVICE_IN' : "InputDevicesMaskType"}
all_criteria = {
'AndroidModeType' : {},
'OutputDevicesMaskType' : {},
'InputDevicesMaskType' : {} }
#
# _CNT, _MAX, _ALL and _NONE are prohibited values as ther are just helpers for enum users.
#
ignored_values = [ 'CNT', 'MAX', 'ALL', 'NONE' ]
criteria_pattern = re.compile(
r"\s*(?P<type>(?:"+'|'.join(criterion_mapping_table.keys()) + "))\_" \
r"(?P<literal>(?!" + '|'.join(ignored_values) + ")\w*)\s*=\s*" \
r"(?P<values>(?:0[xX])?[0-9a-fA-F]+)")
logging.info("Checking Android Header file {}".format(androidaudiobaseheaderFile))
for line_number, line in enumerate(androidaudiobaseheaderFile):
match = criteria_pattern.match(line)
if match:
logging.debug("The following line is VALID: {}:{}\n{}".format(
androidaudiobaseheaderFile.name, line_number, line))
criterion_name = criterion_mapping_table[match.groupdict()['type']]
literal = ''.join((w.capitalize() for w in match.groupdict()['literal'].split('_')))
numerical_value = match.groupdict()['values']
# for AUDIO_DEVICE_IN: need to remove sign bit
if criterion_name == "InputDevicesMaskType":
numerical_value = str(int(numerical_value, 0) & ~2147483648)
# Remove duplicated numerical values
if int(numerical_value, 0) in all_criteria[criterion_name].values():
logging.info("criterion {} duplicated values:".format(criterion_name))
logging.info("{}:{}".format(numerical_value, literal))
logging.info("KEEPING LATEST")
for key in all_criteria[criterion_name].keys():
if all_criteria[criterion_name][key] == int(numerical_value, 0):
del all_criteria[criterion_name][key]
all_criteria[criterion_name][literal] = int(numerical_value, 0)
logging.debug("type:{},".format(criterion_name))
logging.debug("iteral:{},".format(literal))
logging.debug("values:{}.".format(numerical_value))
return all_criteria
def main():
logging.root.setLevel(logging.INFO)
args = parseArgs()
all_criteria = parseAndroidAudioFile(args.androidaudiobaseheader)
address_criteria = parseAndroidAudioPolicyConfigurationFile(args.audiopolicyconfigurationfile)
criterion_types = args.criteriontypes
generateXmlCriterionTypesFile(all_criteria, address_criteria, criterion_types, args.outputfile)
# If this file is directly executed
if __name__ == "__main__":
sys.exit(main())