diff --git a/vndk/tools/sourcedr/README.md b/vndk/tools/sourcedr/README.md index aad530b36..cdf8b1fe0 100644 --- a/vndk/tools/sourcedr/README.md +++ b/vndk/tools/sourcedr/README.md @@ -6,3 +6,5 @@ This is a collection of source tree analysis tools. modules. - [ninja](ninja) analyzes `$(OUT)/combined-${target}.ninja`, which contains all file dependencies. +- [files/list_app_shared_uid.py](files/list_app_shared_uid.py) lists all + pre-installed apps with `sharedUserId`. diff --git a/vndk/tools/sourcedr/files/list_app_shared_uid.py b/vndk/tools/sourcedr/files/list_app_shared_uid.py new file mode 100755 index 000000000..2616e6d77 --- /dev/null +++ b/vndk/tools/sourcedr/files/list_app_shared_uid.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 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. + +"""List all pre-installed Android Apps with `sharedUserId` in their +`AndroidManifest.xml`.""" + +import argparse +import collections +import csv +import json +import os +import re +import subprocess +import sys + + +_SHARED_UID_PATTERN = re.compile('sharedUserId="([^"\\r\\n]*)"') + + +def load_module_paths(module_json): + """Load module source paths.""" + result = {} + with open(module_json, 'r') as json_file: + modules = json.load(json_file) + for name, module in modules.items(): + try: + result[name] = module['path'][0] + except IndexError: + continue + return result + + +def find_shared_uid(manifest_path): + """Extract shared UID from AndroidManifest.xml.""" + try: + with open(manifest_path, 'r') as manifest_file: + content = manifest_file.read() + except UnicodeDecodeError: + return [] + return sorted(_SHARED_UID_PATTERN.findall(content)) + + +def find_file(product_out, app_name): + """Find the APK file for the app.""" + product_out = os.path.abspath(product_out) + prefix_len = len(product_out) + 1 + partitions = ( + 'data', 'odm', 'oem', 'product', 'product_services', 'system', + 'system_other', 'vendor',) + for partition in partitions: + partition_dir = os.path.join(product_out, partition) + for base, _, filenames in os.walk(partition_dir): + for filename in filenames: + name, ext = os.path.splitext(filename) + if name == app_name and ext in {'.apk', '.jar'}: + return os.path.join(base, filename)[prefix_len:] + return '' + + +AppInfo = collections.namedtuple( + 'AppInfo', 'name shared_uid installed_path source_path') + + +def collect_apps_with_shared_uid(product_out, module_paths): + """Collect apps with shared UID.""" + apps_dir = os.path.join(product_out, 'obj', 'APPS') + result = [] + for app_dir_name in os.listdir(apps_dir): + app_name = re.sub('_intermediates$', '', app_dir_name) + app_dir = os.path.join(apps_dir, app_dir_name) + + apk_file = os.path.join(app_dir, 'package.apk') + if not os.path.exists(apk_file): + print('error: Failed to find:', apk_file, file=sys.stderr) + continue + + apk_unpacked = os.path.join(app_dir, 'package') + if not os.path.exists(apk_unpacked): + ret = subprocess.call(['apktool', 'd', 'package.apk'], cwd=app_dir) + if ret != 0: + print('error: Failed to unpack:', apk_file, file=sys.stderr) + continue + + manifest_file = os.path.join(apk_unpacked, 'AndroidManifest.xml') + if not os.path.exists(manifest_file): + print('error: Failed to find:', manifest_file, file=sys.stderr) + continue + + shared_uid = find_shared_uid(manifest_file) + if not shared_uid: + continue + + result.append(AppInfo( + app_name, shared_uid, find_file(product_out, app_name), + module_paths.get(app_name, ''))) + return result + + +def _parse_args(): + """Parse command line options.""" + parser = argparse.ArgumentParser() + parser.add_argument('product_out') + parser.add_argument('-o', '--output', required=True) + return parser.parse_args() + + +def main(): + """Main function.""" + args = _parse_args() + + module_paths = load_module_paths( + os.path.join(args.product_out, 'module-info.json')) + + result = collect_apps_with_shared_uid(args.product_out, module_paths) + + def _generate_sort_key(app): + has_android_uid = any( + uid.startswith('android.uid') for uid in app.shared_uid) + return (not has_android_uid, app.installed_path.startswith('system'), + app.installed_path) + + result.sort(key=_generate_sort_key) + + with open(args.output, 'w') as output_file: + writer = csv.writer(output_file) + writer.writerow(('App Name', 'UID', 'Installation Path', 'Source Path')) + for app in result: + writer.writerow((app.name, ' '.join(app.shared_uid), + app.installed_path, app.source_path)) + + +if __name__ == '__main__': + main()