1# Copyright (C) 2023 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import xml.etree.ElementTree as ET 16import datetime 17import os 18from os import listdir 19from os.path import isfile, join 20from enum import Enum 21import argparse 22import re 23 24 25class ResourceCategory(Enum): 26 Value = 1 27 File = 2 28 Id = 3 29 30 31class AdServicesUiUtil: 32 PUBLIC_XML_DIR = '../apk/publicres/values/public.xml' 33 STRINGS_XML_DIR = '../apk/res/values/strings.xml' 34 COLORS_XML_DIR = '../apk/res/values/colors.xml' 35 DIMENS_XML_DIR = '../apk/res/values/dimens.xml' 36 INTEGERS_XML_DIR = '../apk/res/values/integers.xml' 37 STYLES_XML_DIR = '../apk/res/values/styles.xml' 38 DRAWABLE_XML_DIR = '../apk/res/drawable/' 39 LAYOUT_XML_DIR = '../apk/res/layout/' 40 COPYRIGHT_TEXT = f'''<?xml version="1.0" encoding="utf-8"?> 41<!-- Copyright (C) {datetime.date.today().year} The Android Open Source Project 42 43 Licensed under the Apache License, Version 2.0 (the "License"); 44 you may not use this file except in compliance with the License. 45 You may obtain a copy of the License at 46 47 http://www.apache.org/licenses/LICENSE-2.0 48 49 Unless required by applicable law or agreed to in writing, software 50 distributed under the License is distributed on an "AS IS" BASIS, 51 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 52 See the License for the specific language governing permissions and 53 limitations under the License. 54--> 55<!-- DO NOT MODIFY. This file is automatically generated by generate_adservices_public_xml.py -->\n''' 56 57 def __init__(self): 58 pass 59 60 def _get_next_id(self, existing_mapping, map_start): 61 if existing_mapping: 62 return int(min(existing_mapping.values()), 0) - 1 63 else: 64 return int(map_start, 0) 65 66 def _get_existing_tree(self, dir, node_type): 67 if not os.path.exists(dir): 68 return None, {} 69 else: 70 root = ET.parse(dir).getroot() 71 return root, { 72 node.attrib['name']: node.attrib['id'] 73 for node in root if node.attrib['type'] == node_type 74 } 75 76 def _get_updated_resources(self, res_category, res_xml_dir): 77 match res_category: 78 case ResourceCategory.Value: 79 return [node.attrib['name'] for node in ET.parse(res_xml_dir).getroot()] 80 case ResourceCategory.File: 81 return [file.split('.')[0] for file in listdir(res_xml_dir) if 82 isfile(join(res_xml_dir, file))] 83 case ResourceCategory.Id: 84 new_strings = set() 85 for file in os.scandir(res_xml_dir): 86 for child in ET.parse(file.path).getroot().iter(): 87 id_key = [key for key in child.attrib if re.match(r'\{.*\}id', key)] 88 if len(id_key) > 0: 89 new_strings.add(child.attrib[id_key[0]].split('/')[1]) 90 return new_strings 91 92 def _overwrite_public_xml(self, root, public_xml_dir): 93 if os.path.exists(public_xml_dir): 94 os.remove(public_xml_dir) 95 96 ET.indent(root, space=' ') 97 with open(public_xml_dir, "w+") as file: 98 file.write(self.COPYRIGHT_TEXT) 99 file.write(ET.tostring(root, encoding="unicode")) 100 101 def update_public_xml(self, res_xml_dir, res_type, res_category, map_start, 102 public_xml_dir=PUBLIC_XML_DIR): 103 if not os.path.exists(res_xml_dir): 104 return 105 106 res_all = self._get_updated_resources(res_category, res_xml_dir) 107 root, mapping = self._get_existing_tree(public_xml_dir, res_type) 108 109 # resources of this type already exist in public.xml 110 if mapping: 111 res_new = [res for res in res_all if res not in mapping] 112 # TO-DO: add code to remove exsting elements when needed. 113 res_all = set(res_all) 114 deleted_res = set(res for res in mapping if res not in res_all) 115 # new type of resources are being added in public.xml 116 else: 117 res_new = res_all 118 119 if not res_new: 120 return 121 122 i_min = self._get_next_id(mapping, map_start) 123 for res in res_new: 124 added_element = ET.SubElement(root, 'public') 125 added_element.set('type', res_type) 126 added_element.set('name', res) 127 added_element.set('id', hex(i_min)) 128 i_min -= 1 129 130 self._overwrite_public_xml(root, public_xml_dir) 131 132 def update_adservices_public_xml(self): 133 parser = argparse.ArgumentParser() 134 parser.add_argument("--enable_ota_layout", 135 help="Enable adding all resource types to public.xml for OTA layout updates", 136 action='store_true', default=False) 137 args = parser.parse_args() 138 util.update_public_xml(res_xml_dir=self.STRINGS_XML_DIR, 139 res_type='string', res_category=ResourceCategory.Value, 140 map_start=hex(0x7f017fff)) 141 if args.enable_ota_layout: 142 util.update_public_xml(res_xml_dir=self.DIMENS_XML_DIR, 143 res_type='dimen', res_category=ResourceCategory.Value, 144 map_start=hex(0x7f027fff)) 145 util.update_public_xml(res_xml_dir=self.COLORS_XML_DIR, 146 res_type='color', res_category=ResourceCategory.Value, 147 map_start=hex(0x7f037fff)) 148 util.update_public_xml(res_xml_dir=self.INTEGERS_XML_DIR, 149 res_type='integer', res_category=ResourceCategory.Value, 150 map_start=hex(0x7f047fff)) 151 util.update_public_xml(res_xml_dir=self.STYLES_XML_DIR, 152 res_type='style', res_category=ResourceCategory.Value, 153 map_start=hex(0x7f057fff)) 154 util.update_public_xml(res_xml_dir=self.DRAWABLE_XML_DIR, 155 res_type='drawable', res_category=ResourceCategory.File, 156 map_start=hex(0x7f067fff)) 157 util.update_public_xml(res_xml_dir=self.LAYOUT_XML_DIR, 158 res_type='layout', res_category=ResourceCategory.File, 159 map_start=hex(0x7f077fff)) 160 util.update_public_xml(res_xml_dir=self.LAYOUT_XML_DIR, res_type='id', 161 res_category=ResourceCategory.Id, 162 map_start=hex(0x7f087fff)) 163 164 165if __name__ == '__main__': 166 util = AdServicesUiUtil() 167 util.update_adservices_public_xml() 168