• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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