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