233 lines
8.9 KiB
Python
233 lines
8.9 KiB
Python
# 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, 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"]
|
|
android_app.sharedUserId = root.attributes.get("sharedUserId", "")
|
|
|
|
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"
|
|
android_app.label = application_element.attributes.get("label", "")
|
|
android_app.cross_profile = application_element.attributes.get("crossProfile", "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"
|
|
activity.permission = activity_element.attributes.get("permission", "")
|
|
|
|
parse_intent_filters(activity_element, activity)
|
|
android_app.activities.append(activity)
|
|
|
|
def parse_intent_filters(element, parent):
|
|
for intent_filter_element in find_elements(element.children, "intent-filter"):
|
|
intent_filter = IntentFilter()
|
|
|
|
parse_intent_filter_actions(intent_filter_element, intent_filter)
|
|
parse_intent_filter_category(intent_filter_element, intent_filter)
|
|
parent.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_element.attributes["name"]
|
|
intent_filter.actions.append(action)
|
|
|
|
def parse_intent_filter_category(intent_filter_element, intent_filter):
|
|
for category_element in find_elements(intent_filter_element.children, "category"):
|
|
category = category_element.attributes["name"]
|
|
intent_filter.categories.append(category)
|
|
|
|
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"]
|
|
parse_intent_filters(service_element, service)
|
|
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() |