Merge "sourcedr: List apps with sharedUserId"
diff --git a/vndk/tools/sourcedr/README.md b/vndk/tools/sourcedr/README.md
index aad530b..cdf8b1f 100644
--- a/vndk/tools/sourcedr/README.md
+++ b/vndk/tools/sourcedr/README.md
@@ -6,3 +6,5 @@
   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 0000000..2616e6d
--- /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()