1# Copyright (C) 2021 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 argparse 16from pathlib import Path 17import subprocess 18import queue 19try: 20 from src.library.main.proto.testapp_protos_pb2 import TestAppIndex, AndroidApp, UsesSdk,\ 21 Permission, Activity, ActivityAlias, IntentFilter, Service, Metadata, Receiver 22except ImportError: 23 from google3.third_party.java.bedstead.testapp.src.library.main.proto.testapp_protos_pb2\ 24 import TestAppIndex, AndroidApp, UsesSdk,\ 25 Permission, Activity, ActivityAlias, IntentFilter, Service, Metadata, Receiver 26 27ELEMENT = "E" 28ATTRIBUTE = "A" 29 30def main(): 31 args_parser = argparse.ArgumentParser(description='Generate index for test apps and resources') 32 args_parser.add_argument('--directory', help='Directory containing raw resources') 33 args_parser.add_argument('--aapt2', help='The path to aapt2') 34 args = args_parser.parse_args() 35 36 pathlist = Path(args.directory).rglob('*.apk') 37 file_names = [p.name for p in pathlist] 38 39 index = TestAppIndex() 40 41 for file_name in file_names: 42 aapt2_command = [ 43 args.aapt2, 'd', 'xmltree', '--file', 'AndroidManifest.xml', args.directory + "/" + file_name] 44 index.apps.append(parse(str(subprocess.check_output(aapt2_command)), args.directory, file_name, args.aapt2)) 45 46 with open(args.directory + "/index.txt", "wb") as fd: 47 fd.write(index.SerializeToString()) 48 49class XmlTreeLine: 50 """ A single line taken from the aapt2 xmltree output. """ 51 52 def __init__(self, line, children): 53 self.line = line 54 self.children = children 55 56 def __str__(self): 57 return str(self.line) + "{" + ", ".join([str(s) for s in self.children]) + "}" 58 59class Element: 60 """ An XML element. """ 61 62 def __init__(self, name, attributes, children): 63 self.name = name 64 self.attributes = attributes 65 self.children = children 66 67 def __str__(self): 68 return "Element(" + self.name + " " + str(self.attributes) + ")" 69 70def parse_lines(manifest_content): 71 return parse_line(manifest_content, 0)[1] 72 73def parse_line(manifest_content, ptr, incoming_indentation = -1): 74 line = manifest_content[ptr] 75 line_without_indentation = line.lstrip(" ") 76 indentation_size = len(line) - len(line_without_indentation) 77 78 if (indentation_size <= incoming_indentation): 79 return ptr, None 80 81 ptr += 1 82 children = [] 83 84 while (ptr < len(manifest_content)): 85 ptr, next_child = parse_line(manifest_content, ptr, indentation_size) 86 if next_child: 87 children.append(next_child) 88 else: 89 break 90 91 return ptr, XmlTreeLine(line_without_indentation, children) 92 93def augment(element): 94 """ Convert a XmlTreeLine and descendants into an Element with descendants. """ 95 name = None 96 if element.line: 97 name = element.line[3:].split(" ", 1)[0] 98 attributes = {} 99 children = [] 100 101 children_to_process = queue.Queue() 102 for c in element.children: 103 children_to_process.put(c) 104 105 while not children_to_process.empty(): 106 c = children_to_process.get() 107 if c.line.startswith("E"): 108 # Is an element 109 children.append(augment(c)) 110 elif c.line.startswith("A"): 111 # Is an attribute 112 attribute_name = c.line[3:].split("=", 1)[0] 113 if ":" in attribute_name: 114 attribute_name = attribute_name.rsplit(":", 1)[1] 115 attribute_name = attribute_name.split("(", 1)[0] 116 attribute_value = c.line.split("=", 1)[1].split(" (Raw", 1)[0] 117 if attribute_value[0] == '"': 118 attribute_value = attribute_value[1:-1] 119 attributes[attribute_name] = attribute_value 120 121 # Children of the attribute are actually children of the element itself 122 for child in c.children: 123 children_to_process.put(child) 124 else: 125 raise Exception("Unknown line type for line: " + c.line) 126 127 return Element(name, attributes, children) 128 129def parse(manifest_content, raw_dir, file_name, aapt2): 130 manifest_content = manifest_content.split("\\n") 131 # strip namespaces as not important for our uses 132 # Also strip the last line which is a quotation mark because of the way it's imported 133 manifest_content = [m for m in manifest_content if not "N: " in m][:-1] 134 135 simple_root = parse_lines(manifest_content) 136 root = augment(simple_root) 137 138 android_app = AndroidApp() 139 android_app.apk_name = file_name 140 android_app.package_name = root.attributes["package"] 141 android_app.sharedUserId = root.attributes.get("sharedUserId", "") 142 143 parse_uses_sdk(root, android_app) 144 parse_permissions(root, android_app) 145 146 application_element = find_single_element(root.children, "application") 147 android_app.test_only = application_element.attributes.get("testOnly", "false") == "true" 148 android_app.label = application_element.attributes.get("label", "") 149 android_app.cross_profile = application_element.attributes.get("crossProfile", "false") == "true" 150 151 parse_activity_aliases(application_element, android_app) 152 parse_activities(application_element, android_app) 153 parse_services(application_element, android_app) 154 parse_metadata(application_element, android_app) 155 parse_receiver(application_element, android_app) 156 157 return android_app 158 159def parse_uses_sdk(root, android_app): 160 uses_sdk_element = find_single_element(root.children, "uses-sdk") 161 if uses_sdk_element: 162 if "minSdkVersion" in uses_sdk_element.attributes: 163 try: 164 android_app.uses_sdk.minSdkVersion = int(uses_sdk_element.attributes["minSdkVersion"]) 165 except ValueError: 166 pass 167 if "maxSdkVersion" in uses_sdk_element.attributes: 168 try: 169 android_app.uses_sdk.maxSdkVersion = int(uses_sdk_element.attributes["maxSdkVersion"]) 170 except ValueError: 171 pass 172 if "targetSdkVersion" in uses_sdk_element.attributes: 173 try: 174 android_app.uses_sdk.targetSdkVersion = int(uses_sdk_element.attributes["targetSdkVersion"]) 175 except ValueError: 176 pass 177 178def parse_permissions(root, android_app): 179 for permission_element in find_elements(root.children, "uses-permission"): 180 permission = Permission() 181 permission.name = permission_element.attributes["name"] 182 android_app.permissions.append(permission) 183 184def parse_activities(application_element, android_app): 185 for activity_element in find_elements(application_element.children, "activity"): 186 activity = Activity() 187 188 activity.name = activity_element.attributes["name"] 189 if activity.name.startswith("androidx"): 190 continue # Special case: androidx adds non-logging activities 191 192 activity.exported = activity_element.attributes.get("exported", "false") == "true" 193 activity.permission = activity_element.attributes.get("permission", "") 194 195 parse_intent_filters(activity_element, activity) 196 android_app.activities.append(activity) 197 198def parse_activity_aliases(application_element, android_app): 199 for activity_alias_element in find_elements(application_element.children, "activity-alias"): 200 activity_alias = ActivityAlias() 201 202 activity_alias.name = activity_alias_element.attributes["name"] 203 if activity_alias.name.startswith("androidx"): 204 continue # Special case: androidx adds non-logging activity-aliases 205 206 activity_alias.exported = activity_alias_element.attributes.get("exported", "false") == "true" 207 activity_alias.permission = activity_alias_element.attributes.get("permission", "") 208 209 parse_intent_filters(activity_alias_element, activity_alias) 210 android_app.activityAliases.append(activity_alias) 211 212def parse_intent_filters(element, parent): 213 for intent_filter_element in find_elements(element.children, "intent-filter"): 214 intent_filter = IntentFilter() 215 216 parse_intent_filter_actions(intent_filter_element, intent_filter) 217 parse_intent_filter_category(intent_filter_element, intent_filter) 218 parent.intent_filters.append(intent_filter) 219 220def parse_intent_filter_actions(intent_filter_element, intent_filter): 221 for action_element in find_elements(intent_filter_element.children, "action"): 222 action = action_element.attributes["name"] 223 intent_filter.actions.append(action) 224 225def parse_intent_filter_category(intent_filter_element, intent_filter): 226 for category_element in find_elements(intent_filter_element.children, "category"): 227 category = category_element.attributes["name"] 228 intent_filter.categories.append(category) 229 230def parse_services(application_element, android_app): 231 for service_element in find_elements(application_element.children, "service"): 232 parse_metadata(service_element, android_app) 233 234 service = Service() 235 service.name = service_element.attributes["name"] 236 parse_intent_filters(service_element, service) 237 parse_metadata(service_element, service) 238 android_app.services.append(service) 239 240def parse_metadata(application_element, android_app): 241 for meta_data_element in find_elements(application_element.children, "meta-data"): 242 metadata = Metadata() 243 metadata.name = meta_data_element.attributes["name"] 244 245 if "value" in meta_data_element.attributes: 246 # This forces every value into a string 247 metadata.value = meta_data_element.attributes["value"] 248 if "resource" in meta_data_element.attributes: 249 metadata.resource = meta_data_element.attributes["resource"] 250 251 android_app.metadata.append(metadata) 252 253def parse_receiver(application_element, android_app): 254 for receiver_element in find_elements(application_element.children, "receiver"): 255 parse_metadata(receiver_element, android_app) 256 257 receiver = Receiver() 258 receiver.name = receiver_element.attributes["name"] 259 receiver.permission = receiver_element.attributes.get("permission", "") 260 receiver.exported = receiver_element.attributes.get("exported", "false") == "true" 261 parse_metadata(receiver_element, receiver) 262 parse_intent_filters(receiver_element, receiver) 263 android_app.receivers.append(receiver) 264 265def find_single_element(element_collection, element_name): 266 for e in element_collection: 267 if e.name == element_name: 268 return e 269 270def find_elements(element_collection, element_name): 271 return [e for e in element_collection if e.name == element_name] 272 273if __name__ == "__main__": 274 main()