blob: 0a8f1c9329c3650eeb4a2d5d6b010f316201824e [file] [log] [blame]
# Copyright (C) 2021 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
from pathlib import Path
import subprocess
import queue
from src.library.main.proto.testapp_protos_pb2 import TestAppIndex, AndroidApp, UsesSdk,\
Permission, Activity, IntentFilter, Action, Service, Metadata
ELEMENT = "E"
ATTRIBUTE = "A"
def main():
args_parser = argparse.ArgumentParser(description='Generate index for test apps')
args_parser.add_argument('--directory', help='Directory containing test apps')
args_parser.add_argument('--aapt2', help='The path to aapt2')
args = args_parser.parse_args()
pathlist = Path(args.directory).rglob('*.apk')
file_names = [p.name for p in pathlist]
index = TestAppIndex()
for file_name in file_names:
aapt2_command = [
args.aapt2, 'd', 'xmltree', '--file', 'AndroidManifest.xml', args.directory + "/" + file_name]
index.apps.append(parse(str(subprocess.check_output(aapt2_command)), file_name))
with open(args.directory + "/index.txt", "wb") as fd:
fd.write(index.SerializeToString())
class XmlTreeLine:
""" A single line taken from the aapt2 xmltree output. """
def __init__(self, line, children):
self.line = line
self.children = children
def __str__(self):
return str(self.line) + "{" + ", ".join([str(s) for s in self.children]) + "}"
class Element:
""" An XML element. """
def __init__(self, name, attributes, children):
self.name = name
self.attributes = attributes
self.children = children
def __str__(self):
return "Element(" + self.name + " " + str(self.attributes) + ")"
def parse_lines(manifest_content):
return parse_line(manifest_content, 0)[1]
def parse_line(manifest_content, ptr, incoming_indentation = -1):
line = manifest_content[ptr]
line_without_indentation = line.lstrip(" ")
indentation_size = len(line) - len(line_without_indentation)
if (indentation_size <= incoming_indentation):
return ptr, None
ptr += 1
children = []
while (ptr < len(manifest_content)):
ptr, next_child = parse_line(manifest_content, ptr, indentation_size)
if next_child:
children.append(next_child)
else:
break
return ptr, XmlTreeLine(line_without_indentation, children)
def augment(element):
""" Convert a XmlTreeLine and descendants into an Element with descendants. """
name = None
if element.line:
name = element.line[3:].split(" ", 1)[0]
attributes = {}
children = []
children_to_process = queue.Queue()
for c in element.children:
children_to_process.put(c)
while not children_to_process.empty():
c = children_to_process.get()
if c.line.startswith("E"):
# Is an element
children.append(augment(c))
elif c.line.startswith("A"):
# Is an attribute
attribute_name = c.line[3:].split("=", 1)[0]
if ":" in attribute_name:
attribute_name = attribute_name.rsplit(":", 1)[1]
attribute_name = attribute_name.split("(", 1)[0]
attribute_value = c.line.split("=", 1)[1].split(" (Raw", 1)[0]
if attribute_value[0] == '"':
attribute_value = attribute_value[1:-1]
attributes[attribute_name] = attribute_value
# Children of the attribute are actually children of the element itself
for child in c.children:
children_to_process.put(child)
else:
raise Exception("Unknown line type for line: " + c.line)
return Element(name, attributes, children)
def parse(manifest_content, file_name):
manifest_content = manifest_content.split("\\n")
# strip namespaces as not important for our uses
# Also strip the last line which is a quotation mark because of the way it's imported
manifest_content = [m for m in manifest_content if not "N: " in m][:-1]
simple_root = parse_lines(manifest_content)
root = augment(simple_root)
android_app = AndroidApp()
android_app.apk_name = file_name
android_app.package_name = root.attributes["package"]
parse_uses_sdk(root, android_app)
parse_permissions(root, android_app)
application_element = find_single_element(root.children, "application")
android_app.test_only = application_element.attributes.get("testOnly", "false") == "true"
parse_activities(application_element, android_app)
parse_services(application_element, android_app)
parse_metadata(application_element, android_app)
return android_app
def parse_uses_sdk(root, android_app):
uses_sdk_element = find_single_element(root.children, "uses-sdk")
if uses_sdk_element:
if "minSdkVersion" in uses_sdk_element.attributes:
try:
android_app.uses_sdk.minSdkVersion = int(uses_sdk_element.attributes["minSdkVersion"])
except ValueError:
pass
if "maxSdkVersion" in uses_sdk_element.attributes:
try:
android_app.uses_sdk.maxSdkVersion = int(uses_sdk_element.attributes["maxSdkVersion"])
except ValueError:
pass
if "targetSdkVersion" in uses_sdk_element.attributes:
try:
android_app.uses_sdk.targetSdkVersion = int(uses_sdk_element.attributes["targetSdkVersion"])
except ValueError:
pass
def parse_permissions(root, android_app):
for permission_element in find_elements(root.children, "uses-permission"):
permission = Permission()
permission.name = permission_element.attributes["name"]
android_app.permissions.append(permission)
def parse_activities(application_element, android_app):
for activity_element in find_elements(application_element.children, "activity"):
activity = Activity()
activity.name = activity_element.attributes["name"]
if activity.name.startswith("androidx"):
continue # Special case: androidx adds non-logging activities
activity.exported = activity_element.attributes.get("exported", "false") == "true"
parse_intent_filters(activity_element, activity)
android_app.activities.append(activity)
def parse_intent_filters(activity_element, activity):
for intent_filter_element in find_elements(activity_element.children, "intent-filter"):
intent_filter = IntentFilter()
parse_intent_filter_actions(intent_filter_element, intent_filter)
activity.intent_filters.append(intent_filter)
def parse_intent_filter_actions(intent_filter_element, intent_filter):
for action_element in find_elements(intent_filter_element.children, "action"):
action = Action()
action.name = action_element.attributes["name"]
intent_filter.actions.append(action)
def parse_services(application_element, android_app):
for service_element in find_elements(application_element.children, "service"):
service = Service()
service.name = service_element.attributes["name"]
android_app.services.append(service)
def parse_metadata(application_element, android_app):
for meta_data_element in find_elements(application_element.children, "meta-data"):
metadata = Metadata()
metadata.name = meta_data_element.attributes["name"]
# This forces every value into a string
metadata.value = meta_data_element.attributes["value"]
android_app.metadata.append(metadata)
def find_single_element(element_collection, element_name):
for e in element_collection:
if e.name == element_name:
return e
def find_elements(element_collection, element_name):
return [e for e in element_collection if e.name == element_name]
if __name__ == "__main__":
main()