1#!/usr/bin/env python3 2# 3# Copyright (C) 2018 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""List all pre-installed Android Apps with `sharedUserId` in their 18`AndroidManifest.xml`.""" 19 20import argparse 21import collections 22import csv 23import json 24import os 25import re 26import subprocess 27import sys 28 29 30_SHARED_UID_PATTERN = re.compile('sharedUserId="([^"\\r\\n]*)"') 31 32 33def load_module_paths(module_json): 34 """Load module source paths.""" 35 result = {} 36 with open(module_json, 'r') as json_file: 37 modules = json.load(json_file) 38 for name, module in modules.items(): 39 try: 40 result[name] = module['path'][0] 41 except IndexError: 42 continue 43 return result 44 45 46def find_shared_uid(manifest_path): 47 """Extract shared UID from AndroidManifest.xml.""" 48 try: 49 with open(manifest_path, 'r') as manifest_file: 50 content = manifest_file.read() 51 except UnicodeDecodeError: 52 return [] 53 return sorted(_SHARED_UID_PATTERN.findall(content)) 54 55 56def find_file(product_out, app_name): 57 """Find the APK file for the app.""" 58 product_out = os.path.abspath(product_out) 59 prefix_len = len(product_out) + 1 60 partitions = ( 61 'data', 'odm', 'oem', 'product', 'system', 'system_ext', 'system_other', 62 'vendor',) 63 for partition in partitions: 64 partition_dir = os.path.join(product_out, partition) 65 for base, _, filenames in os.walk(partition_dir): 66 for filename in filenames: 67 name, ext = os.path.splitext(filename) 68 if name == app_name and ext in {'.apk', '.jar'}: 69 return os.path.join(base, filename)[prefix_len:] 70 return '' 71 72 73AppInfo = collections.namedtuple( 74 'AppInfo', 'name shared_uid installed_path source_path') 75 76 77def collect_apps_with_shared_uid(product_out, module_paths): 78 """Collect apps with shared UID.""" 79 apps_dir = os.path.join(product_out, 'obj', 'APPS') 80 result = [] 81 for app_dir_name in os.listdir(apps_dir): 82 app_name = re.sub('_intermediates$', '', app_dir_name) 83 app_dir = os.path.join(apps_dir, app_dir_name) 84 85 apk_file = os.path.join(app_dir, 'package.apk') 86 if not os.path.exists(apk_file): 87 print('error: Failed to find:', apk_file, file=sys.stderr) 88 continue 89 90 apk_unpacked = os.path.join(app_dir, 'package') 91 if not os.path.exists(apk_unpacked): 92 ret = subprocess.call(['apktool', 'd', 'package.apk'], cwd=app_dir) 93 if ret != 0: 94 print('error: Failed to unpack:', apk_file, file=sys.stderr) 95 continue 96 97 manifest_file = os.path.join(apk_unpacked, 'AndroidManifest.xml') 98 if not os.path.exists(manifest_file): 99 print('error: Failed to find:', manifest_file, file=sys.stderr) 100 continue 101 102 shared_uid = find_shared_uid(manifest_file) 103 if not shared_uid: 104 continue 105 106 result.append(AppInfo( 107 app_name, shared_uid, find_file(product_out, app_name), 108 module_paths.get(app_name, ''))) 109 return result 110 111 112def _parse_args(): 113 """Parse command line options.""" 114 parser = argparse.ArgumentParser() 115 parser.add_argument('product_out') 116 parser.add_argument('-o', '--output', required=True) 117 return parser.parse_args() 118 119 120def main(): 121 """Main function.""" 122 args = _parse_args() 123 124 module_paths = load_module_paths( 125 os.path.join(args.product_out, 'module-info.json')) 126 127 result = collect_apps_with_shared_uid(args.product_out, module_paths) 128 129 def _generate_sort_key(app): 130 has_android_uid = any( 131 uid.startswith('android.uid') for uid in app.shared_uid) 132 return (not has_android_uid, app.installed_path.startswith('system'), 133 app.installed_path) 134 135 result.sort(key=_generate_sort_key) 136 137 with open(args.output, 'w') as output_file: 138 writer = csv.writer(output_file) 139 writer.writerow(('App Name', 'UID', 'Installation Path', 'Source Path')) 140 for app in result: 141 writer.writerow((app.name, ' '.join(app.shared_uid), 142 app.installed_path, app.source_path)) 143 144 145if __name__ == '__main__': 146 main() 147