mirror of https://github.com/CalyxOS/scripts
400 lines
15 KiB
Python
Executable File
400 lines
15 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
from collections import OrderedDict
|
|
from glob import glob
|
|
from itertools import product
|
|
import os.path
|
|
import sys
|
|
from xml.etree import ElementTree as ET
|
|
from xml.sax.saxutils import escape, quoteattr
|
|
|
|
from carrier_settings_pb2 import CarrierSettings, MultiCarrierSettings
|
|
from carrier_list_pb2 import CarrierList
|
|
from carrierId_pb2 import CarrierList as CarrierIdList
|
|
|
|
|
|
def indent(elem, level=0):
|
|
"""Based on https://effbot.org/zone/element-lib.htm#prettyprint"""
|
|
i = "\n" + level * " "
|
|
if len(elem):
|
|
if not elem.text or not elem.text.strip():
|
|
elem.text = i + " "
|
|
if not elem.tail or not elem.tail.strip():
|
|
elem.tail = i
|
|
for elem in elem:
|
|
indent(elem, level + 1)
|
|
if not elem.tail or not elem.tail.strip():
|
|
elem.tail = i
|
|
else:
|
|
if level and (not elem.tail or not elem.tail.strip()):
|
|
elem.tail = i
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(
|
|
description="Convert the CarrierSettings protobuf files to XML format compatible with AOSP")
|
|
parser.add_argument('-i', '--input', help='CarrierSettings folder',
|
|
required=True)
|
|
parser.add_argument('-a', '--apns', help='apns-conf.xml Output folder',
|
|
required=False)
|
|
parser.add_argument('-v', '--vendor', help='vendor.xml Output folder',
|
|
required=True)
|
|
return parser.parse_args()
|
|
|
|
|
|
unwanted_configs = [
|
|
# Anything where the value is a package name
|
|
"carrier_app_wake_signal_config",
|
|
"carrier_settings_activity_component_name_string",
|
|
"carrier_setup_app_string",
|
|
"config_ims_package_override_string",
|
|
"enable_apps_string_array",
|
|
"gps.nfw_proxy_apps",
|
|
"smart_forwarding_config_component_name_string",
|
|
"wfc_emergency_address_carrier_app_string",
|
|
# Always allow editing APNs
|
|
"apn_expand_bool",
|
|
"allow_adding_apns_bool",
|
|
"read_only_apn_types_string_array",
|
|
"read_only_apn_fields_string_array"]
|
|
|
|
threshold_configs = ["5g_nr_ssrsrp_thresholds_int_array",
|
|
"5g_nr_sssinr_thresholds_int_array",
|
|
"gsm_rssi_thresholds_int_array",
|
|
"lte_rsrp_thresholds_int_array",
|
|
"lte_rssnr_thresholds_int_array",
|
|
"wcdma_rscp_thresholds_int_array"]
|
|
|
|
def extract_elements(carrier_config_element, config):
|
|
if config.key in unwanted_configs:
|
|
return
|
|
if config.key.startswith("moto_") or "_moto_" in config.key:
|
|
return
|
|
|
|
value_type = config.WhichOneof('value')
|
|
if value_type == 'text_value':
|
|
carrier_config_subelement = ET.SubElement(
|
|
carrier_config_element,
|
|
'string',
|
|
)
|
|
carrier_config_subelement.set('name', config.key)
|
|
carrier_config_subelement.text = getattr(config, value_type)
|
|
elif value_type == 'int_value':
|
|
carrier_config_subelement = ET.SubElement(
|
|
carrier_config_element,
|
|
'int',
|
|
)
|
|
carrier_config_subelement.set('name', config.key)
|
|
carrier_config_subelement.set(
|
|
'value',
|
|
str(getattr(config, value_type)),
|
|
)
|
|
elif value_type == 'long_value':
|
|
carrier_config_subelement = ET.SubElement(
|
|
carrier_config_element,
|
|
'long',
|
|
)
|
|
carrier_config_subelement.set('name', config.key)
|
|
carrier_config_subelement.set(
|
|
'value',
|
|
str(getattr(config, value_type)),
|
|
)
|
|
elif value_type == "bool_value":
|
|
carrier_config_subelement = ET.SubElement(
|
|
carrier_config_element,
|
|
'boolean',
|
|
)
|
|
carrier_config_subelement.set('name', config.key)
|
|
carrier_config_subelement.set(
|
|
'value',
|
|
str(getattr(config, value_type)).lower(),
|
|
)
|
|
elif value_type == 'text_array':
|
|
carrier_config_subelement = ET.SubElement(
|
|
carrier_config_element,
|
|
'string-array',
|
|
)
|
|
carrier_config_subelement.set('name', config.key)
|
|
carrier_config_subelement.set(
|
|
'num',
|
|
str(len(getattr(config, value_type).item)),
|
|
)
|
|
for value in getattr(config, value_type).item:
|
|
carrier_config_item = ET.SubElement(
|
|
carrier_config_subelement,
|
|
'item',
|
|
)
|
|
carrier_config_item.set('value', value)
|
|
elif value_type == 'int_array':
|
|
if config.key in threshold_configs and len(getattr(config, value_type).item) > 4:
|
|
return
|
|
carrier_config_subelement = ET.SubElement(
|
|
carrier_config_element,
|
|
'int-array',
|
|
)
|
|
carrier_config_subelement.set('name', config.key)
|
|
carrier_config_subelement.set(
|
|
'num',
|
|
str(len(getattr(config, value_type).item)),
|
|
)
|
|
for value in getattr(config, value_type).item:
|
|
carrier_config_item = ET.SubElement(
|
|
carrier_config_subelement,
|
|
'item',
|
|
)
|
|
carrier_config_item.set('value', str(value))
|
|
elif value_type == 'bundle':
|
|
carrier_config_subelement = ET.SubElement(
|
|
carrier_config_element,
|
|
'pbundle_as_map',
|
|
)
|
|
carrier_config_subelement.set('name', config.key)
|
|
for value in getattr(config, value_type).config:
|
|
extract_elements(carrier_config_subelement, value)
|
|
elif value_type == 'double_value':
|
|
carrier_config_subelement = ET.SubElement(
|
|
carrier_config_element,
|
|
'double',
|
|
)
|
|
carrier_config_subelement.set('name', config.key)
|
|
carrier_config_subelement.set(
|
|
'value',
|
|
str(getattr(config, value_type)),
|
|
)
|
|
else:
|
|
raise TypeError("Unknown value type: {}".format(value_type))
|
|
|
|
|
|
def extract_gps_elements(carrier_config_element, config):
|
|
if not config.key.startswith('gps.'):
|
|
return
|
|
extract_elements(carrier_config_element, config)
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
|
|
input_folder = args.input
|
|
apns_folder = args.apns
|
|
vendor_folder = args.vendor
|
|
|
|
carrier_id_list = CarrierIdList()
|
|
carrier_attribute_map = {}
|
|
with open(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'carrier_list.pb'), 'rb') as pb:
|
|
carrier_id_list.ParseFromString(pb.read())
|
|
for carrier_id_obj in carrier_id_list.carrier_id:
|
|
for carrier_attribute in carrier_id_obj.carrier_attribute:
|
|
for carrier_attributes in product(*(
|
|
(s.lower() for s in getattr(carrier_attribute, i) or [''])
|
|
for i in [
|
|
'mccmnc_tuple', 'imsi_prefix_xpattern', 'spn', 'plmn',
|
|
'gid1', 'preferred_apn', 'iccid_prefix',
|
|
'privilege_access_rule',
|
|
]
|
|
)):
|
|
carrier_attribute_map[carrier_attributes] = \
|
|
carrier_id_obj.canonical_id
|
|
|
|
carrier_list = CarrierList()
|
|
all_settings = {}
|
|
|
|
carrier_list.ParseFromString(open(os.path.join(input_folder, 'carrier_list.pb'), 'rb').read())
|
|
# Load generic settings first
|
|
multi_settings = MultiCarrierSettings()
|
|
multi_settings.ParseFromString(open(os.path.join(input_folder, 'others.pb'), 'rb').read())
|
|
for setting in multi_settings.setting:
|
|
all_settings[setting.canonical_name] = setting
|
|
# Load carrier specific files last, to allow overriding generic settings
|
|
for filename in glob(os.path.join(input_folder, '*.pb')):
|
|
with open(filename, 'rb') as pb:
|
|
if os.path.basename(filename) == 'carrier_list.pb':
|
|
# Handled above already
|
|
continue
|
|
elif os.path.basename(filename) == 'others.pb':
|
|
# Handled above already
|
|
continue
|
|
else:
|
|
setting = CarrierSettings()
|
|
setting.ParseFromString(pb.read())
|
|
if setting.canonical_name in all_settings:
|
|
print("Overriding generic settings for " + setting.canonical_name, file=sys.stderr)
|
|
all_settings[setting.canonical_name] = setting
|
|
|
|
carrier_config_root = ET.Element('carrier_config_list')
|
|
carrier_config_no_sim_root = ET.Element('carrier_config_list')
|
|
|
|
# Unfortunately, python processors like xml and lxml, as well as command-line
|
|
# utilities like tidy, do not support the exact style used by AOSP for
|
|
# apns-conf.xml:
|
|
#
|
|
# * indent: 2 spaces
|
|
# * attribute indent: 4 spaces
|
|
# * blank lines between elements
|
|
# * attributes after first indented on separate lines
|
|
# * closing tags of multi-line elements on separate, unindented lines
|
|
#
|
|
# Therefore, we build the file without using an XML processor.
|
|
|
|
class ApnElement:
|
|
def __init__(self, apn, carrier_id):
|
|
self.apn = apn
|
|
self.carrier_id = carrier_id
|
|
self.attributes = OrderedDict()
|
|
self.add_attributes()
|
|
|
|
def add_attribute(self, key, field=None, value=None):
|
|
if value is not None:
|
|
self.attributes[key] = value
|
|
else:
|
|
if field is None:
|
|
field = key
|
|
if self.apn.HasField(field):
|
|
enum_type = self.apn.DESCRIPTOR.fields_by_name[field].enum_type
|
|
value = getattr(self.apn, field)
|
|
if key == 'skip_464xlat':
|
|
self.attributes[key] = str(value - 1)
|
|
elif enum_type is None:
|
|
if isinstance(value, bool):
|
|
self.attributes[key] = str(value).lower()
|
|
else:
|
|
self.attributes[key] = str(value)
|
|
else:
|
|
self.attributes[key] = \
|
|
enum_type.values_by_number[value].name
|
|
|
|
def add_attributes(self):
|
|
try:
|
|
self.add_attribute(
|
|
'carrier_id',
|
|
value=str(carrier_attribute_map[(
|
|
self.carrier_id.mcc_mnc,
|
|
self.carrier_id.imsi,
|
|
self.carrier_id.spn.lower(),
|
|
'',
|
|
self.carrier_id.gid1.lower(),
|
|
'',
|
|
'',
|
|
'',
|
|
)])
|
|
)
|
|
except KeyError:
|
|
pass
|
|
self.add_attribute('mcc', value=self.carrier_id.mcc_mnc[:3])
|
|
self.add_attribute('mnc', value=self.carrier_id.mcc_mnc[3:])
|
|
self.add_attribute('apn', 'value')
|
|
self.add_attribute('proxy')
|
|
self.add_attribute('port')
|
|
self.add_attribute('mmsc')
|
|
self.add_attribute('mmsproxy', 'mmsc_proxy')
|
|
self.add_attribute('mmsport', 'mmsc_proxy_port')
|
|
self.add_attribute('user')
|
|
self.add_attribute('password')
|
|
self.add_attribute('server')
|
|
self.add_attribute('authtype')
|
|
self.add_attribute(
|
|
'type',
|
|
value=','.join(
|
|
apn.DESCRIPTOR.fields_by_name[
|
|
'type'
|
|
].enum_type.values_by_number[i].name
|
|
for i in self.apn.type
|
|
).lower(),
|
|
)
|
|
self.add_attribute('protocol')
|
|
self.add_attribute('roaming_protocol')
|
|
self.add_attribute('carrier_enabled')
|
|
self.add_attribute('bearer_bitmask')
|
|
self.add_attribute('profile_id')
|
|
self.add_attribute('modem_cognitive')
|
|
self.add_attribute('max_conns')
|
|
self.add_attribute('wait_time')
|
|
self.add_attribute('max_conns_time')
|
|
self.add_attribute('mtu')
|
|
mvno = self.carrier_id.WhichOneof('mvno_data')
|
|
if mvno:
|
|
self.add_attribute(
|
|
'mvno_type',
|
|
value='gid' if mvno.startswith('gid') else mvno,
|
|
)
|
|
self.add_attribute(
|
|
'mvno_match_data',
|
|
value=getattr(self.carrier_id, mvno),
|
|
)
|
|
self.add_attribute('apn_set_id')
|
|
# No source for integer carrier_id?
|
|
self.add_attribute('skip_464xlat')
|
|
self.add_attribute('user_visible')
|
|
self.add_attribute('user_editable')
|
|
|
|
if apns_folder is not None:
|
|
with open(os.path.join(apns_folder, 'apns-conf.xml'), 'w', encoding='utf-8') as f:
|
|
f.write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n\n')
|
|
f.write('<apns version="8">\n\n')
|
|
|
|
for entry in carrier_list.entry:
|
|
try:
|
|
setting = all_settings[entry.canonical_name]
|
|
except KeyError:
|
|
print("Skipping " + entry.canonical_name, file=sys.stderr)
|
|
continue
|
|
for apn in setting.apns.apn:
|
|
f.write(' <apn carrier={}\n'.format(quoteattr(apn.name)))
|
|
apn_element = ApnElement(apn, entry.carrier_id[0])
|
|
for (key, value) in apn_element.attributes.items():
|
|
f.write(' {}={}\n'.format(escape(key), quoteattr(value)))
|
|
f.write(' />\n\n')
|
|
|
|
f.write('</apns>\n')
|
|
|
|
# Test XML parsing.
|
|
ET.parse(os.path.join(apns_folder, 'apns-conf.xml'))
|
|
|
|
for entry in carrier_list.entry:
|
|
try:
|
|
setting = all_settings[entry.canonical_name]
|
|
except KeyError:
|
|
print("Skipping " + entry.canonical_name, file=sys.stderr)
|
|
continue
|
|
|
|
carrier_config_element = ET.SubElement(
|
|
carrier_config_root,
|
|
'carrier_config',
|
|
)
|
|
mcc = entry.carrier_id[0].mcc_mnc[:3]
|
|
mnc = entry.carrier_id[0].mcc_mnc[3:]
|
|
if (mcc == '000' and mnc == '000'):
|
|
carrier_config_no_sim_element = ET.SubElement(
|
|
carrier_config_no_sim_root,
|
|
'carrier_config',
|
|
)
|
|
for config in setting.configs.config:
|
|
extract_gps_elements(carrier_config_no_sim_element, config)
|
|
else:
|
|
carrier_config_element.set('mcc', mcc)
|
|
carrier_config_element.set('mnc', mnc)
|
|
for field in ['spn', 'imsi', 'gid1']:
|
|
if entry.carrier_id[0].HasField(field):
|
|
carrier_config_element.set(
|
|
field,
|
|
getattr(entry.carrier_id[0], field),
|
|
)
|
|
for config in setting.configs.config:
|
|
extract_elements(carrier_config_element, config)
|
|
|
|
indent(carrier_config_root)
|
|
carrier_config_tree = ET.ElementTree(carrier_config_root)
|
|
carrier_config_tree.write(os.path.join(vendor_folder, 'vendor.xml'),
|
|
encoding='utf-8', xml_declaration=True)
|
|
indent(carrier_config_no_sim_root)
|
|
carrier_config_no_sim_tree = ET.ElementTree(carrier_config_no_sim_root)
|
|
carrier_config_no_sim_tree.write(os.path.join(vendor_folder, 'vendor_no_sim.xml'),
|
|
encoding='utf-8', xml_declaration=True)
|
|
|
|
# Test XML parsing.
|
|
ET.parse(os.path.join(vendor_folder, 'vendor.xml'))
|
|
ET.parse(os.path.join(vendor_folder, 'vendor_no_sim.xml'))
|
|
|
|
if __name__ == '__main__':
|
|
main()
|