Home | History | Annotate | Download | only in tools
      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 
     19 import argparse
     20 import re
     21 import sys
     22 import tempfile
     23 import os
     24 import logging
     25 import subprocess
     26 import xml.etree.ElementTree as ET
     27 import xml.etree.ElementInclude as EI
     28 import xml.dom.minidom as MINIDOM
     29 from 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 
     50 def 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 
     83 def 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 
    125 def 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 #
    132 def 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 #
    179 def 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 
    237 def 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
    250 if __name__ == "__main__":
    251     sys.exit(main())
    252