xref: /aosp_15_r20/development/tools/privapp_permissions/privapp_permissions.py (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1*90c8c64dSAndroid Build Coastguard Worker#!/usr/bin/env python
2*90c8c64dSAndroid Build Coastguard Worker#
3*90c8c64dSAndroid Build Coastguard Worker#   Copyright 2017 - The Android Open Source Project
4*90c8c64dSAndroid Build Coastguard Worker#
5*90c8c64dSAndroid Build Coastguard Worker#   Licensed under the Apache License, Version 2.0 (the "License");
6*90c8c64dSAndroid Build Coastguard Worker#   you may not use this file except in compliance with the License.
7*90c8c64dSAndroid Build Coastguard Worker#   You may obtain a copy of the License at
8*90c8c64dSAndroid Build Coastguard Worker#
9*90c8c64dSAndroid Build Coastguard Worker#       http://www.apache.org/licenses/LICENSE-2.0
10*90c8c64dSAndroid Build Coastguard Worker#
11*90c8c64dSAndroid Build Coastguard Worker#   Unless required by applicable law or agreed to in writing, software
12*90c8c64dSAndroid Build Coastguard Worker#   distributed under the License is distributed on an "AS IS" BASIS,
13*90c8c64dSAndroid Build Coastguard Worker#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*90c8c64dSAndroid Build Coastguard Worker#   See the License for the specific language governing permissions and
15*90c8c64dSAndroid Build Coastguard Worker#   limitations under the License.
16*90c8c64dSAndroid Build Coastguard Worker
17*90c8c64dSAndroid Build Coastguard Workerfrom __future__ import print_function
18*90c8c64dSAndroid Build Coastguard Workerfrom xml.dom import minidom
19*90c8c64dSAndroid Build Coastguard Worker
20*90c8c64dSAndroid Build Coastguard Workerimport argparse
21*90c8c64dSAndroid Build Coastguard Workerimport itertools
22*90c8c64dSAndroid Build Coastguard Workerimport os
23*90c8c64dSAndroid Build Coastguard Workerimport re
24*90c8c64dSAndroid Build Coastguard Workerimport subprocess
25*90c8c64dSAndroid Build Coastguard Workerimport sys
26*90c8c64dSAndroid Build Coastguard Workerimport tempfile
27*90c8c64dSAndroid Build Coastguard Workerimport shutil
28*90c8c64dSAndroid Build Coastguard Worker
29*90c8c64dSAndroid Build Coastguard WorkerDEVICE_PREFIX = 'device:'
30*90c8c64dSAndroid Build Coastguard WorkerANDROID_NAME_REGEX = r'A: android:name\([\S]+\)=\"([\S]+)\"'
31*90c8c64dSAndroid Build Coastguard WorkerANDROID_PROTECTION_LEVEL_REGEX = \
32*90c8c64dSAndroid Build Coastguard Worker    r'A: android:protectionLevel\([^\)]+\)=\(type [\S]+\)0x([\S]+)'
33*90c8c64dSAndroid Build Coastguard WorkerBASE_XML_FILENAME = 'privapp-permissions-platform.xml'
34*90c8c64dSAndroid Build Coastguard Worker
35*90c8c64dSAndroid Build Coastguard WorkerHELP_MESSAGE = """\
36*90c8c64dSAndroid Build Coastguard WorkerGenerates privapp-permissions.xml file for priv-apps.
37*90c8c64dSAndroid Build Coastguard Worker
38*90c8c64dSAndroid Build Coastguard WorkerUsage:
39*90c8c64dSAndroid Build Coastguard Worker    Specify which apk to generate priv-app permissions for. If no apk is \
40*90c8c64dSAndroid Build Coastguard Workerspecified, this will default to all APKs under "<ANDROID_PRODUCT_OUT>/\
41*90c8c64dSAndroid Build Coastguard Worker<all the partitions>/priv-app/".
42*90c8c64dSAndroid Build Coastguard Worker
43*90c8c64dSAndroid Build Coastguard Worker    To specify a target partition(s), use "-p <PARTITION>," where <PARTITION> \
44*90c8c64dSAndroid Build Coastguard Workercan be "system", "product", "system/product", "system_ext", \
45*90c8c64dSAndroid Build Coastguard Worker"system/system_ext", "system,system/product,vendor,system_ext", etc.
46*90c8c64dSAndroid Build Coastguard Worker
47*90c8c64dSAndroid Build Coastguard Worker    When using adb, adb pull can take a long time. To see the adb pull \
48*90c8c64dSAndroid Build Coastguard Workerprogress, use "-v"
49*90c8c64dSAndroid Build Coastguard Worker
50*90c8c64dSAndroid Build Coastguard WorkerExamples:
51*90c8c64dSAndroid Build Coastguard Worker
52*90c8c64dSAndroid Build Coastguard Worker    For all APKs under $ANDROID_PRODUCT_OUT/<all partitions>/priv-app/:
53*90c8c64dSAndroid Build Coastguard Worker        # If the build environment has not been set up, do so:
54*90c8c64dSAndroid Build Coastguard Worker        . build/envsetup.sh
55*90c8c64dSAndroid Build Coastguard Worker        lunch product_name
56*90c8c64dSAndroid Build Coastguard Worker        m -j32
57*90c8c64dSAndroid Build Coastguard Worker        # then use:
58*90c8c64dSAndroid Build Coastguard Worker        cd development/tools/privapp_permissions/
59*90c8c64dSAndroid Build Coastguard Worker        ./privapp_permissions.py
60*90c8c64dSAndroid Build Coastguard Worker        # or to search for apks in "product" partition
61*90c8c64dSAndroid Build Coastguard Worker        ./privapp_permissions.py -p product
62*90c8c64dSAndroid Build Coastguard Worker        # or to search for apks in system, product, and vendor partitions
63*90c8c64dSAndroid Build Coastguard Worker        ./privapp_permissions.py -p system,product,vendor
64*90c8c64dSAndroid Build Coastguard Worker
65*90c8c64dSAndroid Build Coastguard Worker    For an APK against $ANDROID_PRODUCT_OUT/<all partitions>/etc/permissions/:
66*90c8c64dSAndroid Build Coastguard Worker        ./privapp_permissions.py path/to/the.apk
67*90c8c64dSAndroid Build Coastguard Worker        # or against /product/etc/permissions/
68*90c8c64dSAndroid Build Coastguard Worker        ./privapp_permissions.py path/to/the.apk -p product
69*90c8c64dSAndroid Build Coastguard Worker
70*90c8c64dSAndroid Build Coastguard Worker    For an APK already on the device against /<all partitions>/etc/permissions/:
71*90c8c64dSAndroid Build Coastguard Worker        ./privapp_permissions.py device:/device/path/to/the.apk
72*90c8c64dSAndroid Build Coastguard Worker        # or against /product/etc/permissions/
73*90c8c64dSAndroid Build Coastguard Worker        ./privapp_permissions.py path/to/the.apk -p product
74*90c8c64dSAndroid Build Coastguard Worker
75*90c8c64dSAndroid Build Coastguard Worker    For all APKs on a device under /<all partitions>/priv-app/:
76*90c8c64dSAndroid Build Coastguard Worker        ./privapp_permissions.py -d
77*90c8c64dSAndroid Build Coastguard Worker        # or if more than one device is attached
78*90c8c64dSAndroid Build Coastguard Worker        ./privapp_permissions.py -s <ANDROID_SERIAL>
79*90c8c64dSAndroid Build Coastguard Worker        # or for all APKs on the "system" partitions
80*90c8c64dSAndroid Build Coastguard Worker        ./privapp_permissions.py -d -p system
81*90c8c64dSAndroid Build Coastguard Worker"""
82*90c8c64dSAndroid Build Coastguard Worker
83*90c8c64dSAndroid Build Coastguard Worker# An array of all generated temp directories.
84*90c8c64dSAndroid Build Coastguard Workertemp_dirs = []
85*90c8c64dSAndroid Build Coastguard Worker# An array of all generated temp files.
86*90c8c64dSAndroid Build Coastguard Workertemp_files = []
87*90c8c64dSAndroid Build Coastguard Worker
88*90c8c64dSAndroid Build Coastguard Workerdef vprint(enable, message, *args):
89*90c8c64dSAndroid Build Coastguard Worker    if enable:
90*90c8c64dSAndroid Build Coastguard Worker        # Use stderr to avoid poluting print_xml result
91*90c8c64dSAndroid Build Coastguard Worker        sys.stderr.write(message % args + '\n')
92*90c8c64dSAndroid Build Coastguard Worker
93*90c8c64dSAndroid Build Coastguard Workerclass MissingResourceError(Exception):
94*90c8c64dSAndroid Build Coastguard Worker    """Raised when a dependency cannot be located."""
95*90c8c64dSAndroid Build Coastguard Worker
96*90c8c64dSAndroid Build Coastguard Worker
97*90c8c64dSAndroid Build Coastguard Workerclass Adb(object):
98*90c8c64dSAndroid Build Coastguard Worker    """A small wrapper around ADB calls."""
99*90c8c64dSAndroid Build Coastguard Worker
100*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, path, serial=None, verbose=False):
101*90c8c64dSAndroid Build Coastguard Worker        self.path = path
102*90c8c64dSAndroid Build Coastguard Worker        self.serial = serial
103*90c8c64dSAndroid Build Coastguard Worker        self.verbose = verbose
104*90c8c64dSAndroid Build Coastguard Worker
105*90c8c64dSAndroid Build Coastguard Worker    def pull(self, src, dst=None):
106*90c8c64dSAndroid Build Coastguard Worker        """A wrapper for `adb -s <SERIAL> pull <src> <dst>`.
107*90c8c64dSAndroid Build Coastguard Worker        Args:
108*90c8c64dSAndroid Build Coastguard Worker            src: The source path on the device
109*90c8c64dSAndroid Build Coastguard Worker            dst: The destination path on the host
110*90c8c64dSAndroid Build Coastguard Worker
111*90c8c64dSAndroid Build Coastguard Worker        Throws:
112*90c8c64dSAndroid Build Coastguard Worker            subprocess.CalledProcessError upon pull failure.
113*90c8c64dSAndroid Build Coastguard Worker        """
114*90c8c64dSAndroid Build Coastguard Worker        if not dst:
115*90c8c64dSAndroid Build Coastguard Worker            if self.call('shell \'if [ -d "%s" ]; then echo True; fi\'' % src):
116*90c8c64dSAndroid Build Coastguard Worker                dst = tempfile.mkdtemp()
117*90c8c64dSAndroid Build Coastguard Worker                temp_dirs.append(dst)
118*90c8c64dSAndroid Build Coastguard Worker            else:
119*90c8c64dSAndroid Build Coastguard Worker                _, dst = tempfile.mkstemp()
120*90c8c64dSAndroid Build Coastguard Worker                temp_files.append(dst)
121*90c8c64dSAndroid Build Coastguard Worker        self.call('pull %s %s' % (src, dst), False, self.verbose)
122*90c8c64dSAndroid Build Coastguard Worker        return dst
123*90c8c64dSAndroid Build Coastguard Worker
124*90c8c64dSAndroid Build Coastguard Worker    def call(self, cmdline, getoutput=True, verbose=False):
125*90c8c64dSAndroid Build Coastguard Worker        """Calls an adb command.
126*90c8c64dSAndroid Build Coastguard Worker
127*90c8c64dSAndroid Build Coastguard Worker        Throws:
128*90c8c64dSAndroid Build Coastguard Worker            subprocess.CalledProcessError upon command failure.
129*90c8c64dSAndroid Build Coastguard Worker        """
130*90c8c64dSAndroid Build Coastguard Worker        command = '%s -s %s %s' % (self.path, self.serial, cmdline)
131*90c8c64dSAndroid Build Coastguard Worker        if getoutput:
132*90c8c64dSAndroid Build Coastguard Worker            return get_output(command)
133*90c8c64dSAndroid Build Coastguard Worker        else:
134*90c8c64dSAndroid Build Coastguard Worker            # Handle verbose mode only when the output is not needed
135*90c8c64dSAndroid Build Coastguard Worker            # This is mainly for adb pull, which can take a long time
136*90c8c64dSAndroid Build Coastguard Worker            extracmd = ' > /dev/null 2>&1'
137*90c8c64dSAndroid Build Coastguard Worker            if verbose:
138*90c8c64dSAndroid Build Coastguard Worker                # Use stderr to avoid poluting print_xml result
139*90c8c64dSAndroid Build Coastguard Worker                extracmd = ' 1>&2'
140*90c8c64dSAndroid Build Coastguard Worker            os.system(command + extracmd)
141*90c8c64dSAndroid Build Coastguard Worker
142*90c8c64dSAndroid Build Coastguard Workerclass Aapt(object):
143*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, path):
144*90c8c64dSAndroid Build Coastguard Worker        self.path = path
145*90c8c64dSAndroid Build Coastguard Worker
146*90c8c64dSAndroid Build Coastguard Worker    def call(self, arguments):
147*90c8c64dSAndroid Build Coastguard Worker        """Run an aapt command with the given args.
148*90c8c64dSAndroid Build Coastguard Worker
149*90c8c64dSAndroid Build Coastguard Worker        Args:
150*90c8c64dSAndroid Build Coastguard Worker            arguments: a list of string arguments
151*90c8c64dSAndroid Build Coastguard Worker        Returns:
152*90c8c64dSAndroid Build Coastguard Worker            The output of the aapt command as a string.
153*90c8c64dSAndroid Build Coastguard Worker        """
154*90c8c64dSAndroid Build Coastguard Worker        output = subprocess.check_output([self.path] + arguments,
155*90c8c64dSAndroid Build Coastguard Worker                                         stderr=subprocess.STDOUT)
156*90c8c64dSAndroid Build Coastguard Worker        return output.decode(encoding='UTF-8')
157*90c8c64dSAndroid Build Coastguard Worker
158*90c8c64dSAndroid Build Coastguard Worker
159*90c8c64dSAndroid Build Coastguard Workerclass Resources(object):
160*90c8c64dSAndroid Build Coastguard Worker    """A class that contains the resources needed to generate permissions.
161*90c8c64dSAndroid Build Coastguard Worker
162*90c8c64dSAndroid Build Coastguard Worker    Attributes:
163*90c8c64dSAndroid Build Coastguard Worker        adb: A wrapper class around ADB with a default serial. Only needed when
164*90c8c64dSAndroid Build Coastguard Worker             using -d, -s, or "device:"
165*90c8c64dSAndroid Build Coastguard Worker        _aapt_path: The path to aapt.
166*90c8c64dSAndroid Build Coastguard Worker    """
167*90c8c64dSAndroid Build Coastguard Worker
168*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, adb_path=None, aapt_path=None, use_device=None,
169*90c8c64dSAndroid Build Coastguard Worker                 serial=None, partitions=None, verbose=False,
170*90c8c64dSAndroid Build Coastguard Worker                 writetodisk=None, systemfile=None, productfile=None,
171*90c8c64dSAndroid Build Coastguard Worker                 apks=None):
172*90c8c64dSAndroid Build Coastguard Worker        self.adb = Resources._resolve_adb(adb_path)
173*90c8c64dSAndroid Build Coastguard Worker        self.aapt = Resources._resolve_aapt(aapt_path)
174*90c8c64dSAndroid Build Coastguard Worker
175*90c8c64dSAndroid Build Coastguard Worker        self.verbose = self.adb.verbose = verbose
176*90c8c64dSAndroid Build Coastguard Worker        self.writetodisk = writetodisk
177*90c8c64dSAndroid Build Coastguard Worker        self.systemfile = systemfile;
178*90c8c64dSAndroid Build Coastguard Worker        self.productfile = productfile;
179*90c8c64dSAndroid Build Coastguard Worker
180*90c8c64dSAndroid Build Coastguard Worker        self._is_android_env = 'ANDROID_PRODUCT_OUT' in os.environ and \
181*90c8c64dSAndroid Build Coastguard Worker                               'ANDROID_HOST_OUT' in os.environ
182*90c8c64dSAndroid Build Coastguard Worker        use_device = use_device or serial or \
183*90c8c64dSAndroid Build Coastguard Worker                     (apks and DEVICE_PREFIX in '&'.join(apks))
184*90c8c64dSAndroid Build Coastguard Worker
185*90c8c64dSAndroid Build Coastguard Worker        self.adb.serial = self._resolve_serial(use_device, serial)
186*90c8c64dSAndroid Build Coastguard Worker
187*90c8c64dSAndroid Build Coastguard Worker        if self.adb.serial:
188*90c8c64dSAndroid Build Coastguard Worker            self.adb.call('root')
189*90c8c64dSAndroid Build Coastguard Worker            self.adb.call('wait-for-device')
190*90c8c64dSAndroid Build Coastguard Worker
191*90c8c64dSAndroid Build Coastguard Worker        if self.adb.serial is None and not self._is_android_env:
192*90c8c64dSAndroid Build Coastguard Worker            raise MissingResourceError(
193*90c8c64dSAndroid Build Coastguard Worker                'You must either set up your build environment, or specify a '
194*90c8c64dSAndroid Build Coastguard Worker                'device to run against. See --help for more info.')
195*90c8c64dSAndroid Build Coastguard Worker
196*90c8c64dSAndroid Build Coastguard Worker        if apks and (partitions == "all" or partitions.find(',') != -1):
197*90c8c64dSAndroid Build Coastguard Worker            # override the partition to "system
198*90c8c64dSAndroid Build Coastguard Worker            print('\n# Defaulting the target partition to "system". '
199*90c8c64dSAndroid Build Coastguard Worker                  'Use -p option to specify the target partition '
200*90c8c64dSAndroid Build Coastguard Worker                  '(must provide one target instead of a list).\n',
201*90c8c64dSAndroid Build Coastguard Worker                  file=sys.stderr)
202*90c8c64dSAndroid Build Coastguard Worker            partitions = "system"
203*90c8c64dSAndroid Build Coastguard Worker
204*90c8c64dSAndroid Build Coastguard Worker        if partitions == "all":
205*90c8c64dSAndroid Build Coastguard Worker            # This is the default scenario
206*90c8c64dSAndroid Build Coastguard Worker            # Find all the partitions where priv-app exists
207*90c8c64dSAndroid Build Coastguard Worker            self.partitions = self._get_partitions()
208*90c8c64dSAndroid Build Coastguard Worker        else:
209*90c8c64dSAndroid Build Coastguard Worker            # Initialize self.partitions with the specified partitions
210*90c8c64dSAndroid Build Coastguard Worker            self.partitions = []
211*90c8c64dSAndroid Build Coastguard Worker            for p in partitions.split(','):
212*90c8c64dSAndroid Build Coastguard Worker                if p.endswith('/'):
213*90c8c64dSAndroid Build Coastguard Worker                    p = p[:-1]
214*90c8c64dSAndroid Build Coastguard Worker                self.partitions.append(p)
215*90c8c64dSAndroid Build Coastguard Worker                # Check if the directory exists
216*90c8c64dSAndroid Build Coastguard Worker                self._check_dir(p + '/priv-app')
217*90c8c64dSAndroid Build Coastguard Worker
218*90c8c64dSAndroid Build Coastguard Worker        vprint(self.verbose,
219*90c8c64dSAndroid Build Coastguard Worker                '# Examining the partitions: ' + str(self.partitions))
220*90c8c64dSAndroid Build Coastguard Worker
221*90c8c64dSAndroid Build Coastguard Worker        # Create dictionary of array (partition as the key)
222*90c8c64dSAndroid Build Coastguard Worker        self.privapp_apks = self._resolve_apks(apks, self.partitions)
223*90c8c64dSAndroid Build Coastguard Worker        self.permissions_dirs = self._resolve_sys_paths('etc/permissions',
224*90c8c64dSAndroid Build Coastguard Worker                                                       self.partitions)
225*90c8c64dSAndroid Build Coastguard Worker        self.sysconfig_dirs = self._resolve_sys_paths('etc/sysconfig',
226*90c8c64dSAndroid Build Coastguard Worker                                                     self.partitions)
227*90c8c64dSAndroid Build Coastguard Worker
228*90c8c64dSAndroid Build Coastguard Worker        # Always use the one in /system partition,
229*90c8c64dSAndroid Build Coastguard Worker        # as that is the only place we will find framework-res.apk
230*90c8c64dSAndroid Build Coastguard Worker        self.framework_res_apk = self._resolve_sys_path('system/framework/'
231*90c8c64dSAndroid Build Coastguard Worker                                                        'framework-res.apk')
232*90c8c64dSAndroid Build Coastguard Worker    @staticmethod
233*90c8c64dSAndroid Build Coastguard Worker    def _resolve_adb(adb_path):
234*90c8c64dSAndroid Build Coastguard Worker        """Resolves ADB from either the cmdline argument or the os environment.
235*90c8c64dSAndroid Build Coastguard Worker
236*90c8c64dSAndroid Build Coastguard Worker        Args:
237*90c8c64dSAndroid Build Coastguard Worker            adb_path: The argument passed in for adb. Can be None.
238*90c8c64dSAndroid Build Coastguard Worker        Returns:
239*90c8c64dSAndroid Build Coastguard Worker            An Adb object.
240*90c8c64dSAndroid Build Coastguard Worker        Raises:
241*90c8c64dSAndroid Build Coastguard Worker            MissingResourceError if adb cannot be resolved.
242*90c8c64dSAndroid Build Coastguard Worker        """
243*90c8c64dSAndroid Build Coastguard Worker        if adb_path:
244*90c8c64dSAndroid Build Coastguard Worker            if os.path.isfile(adb_path):
245*90c8c64dSAndroid Build Coastguard Worker                adb = adb_path
246*90c8c64dSAndroid Build Coastguard Worker            else:
247*90c8c64dSAndroid Build Coastguard Worker                raise MissingResourceError('Cannot resolve adb: No such file '
248*90c8c64dSAndroid Build Coastguard Worker                                           '"%s" exists.' % adb_path)
249*90c8c64dSAndroid Build Coastguard Worker        else:
250*90c8c64dSAndroid Build Coastguard Worker            try:
251*90c8c64dSAndroid Build Coastguard Worker                adb = get_output('which adb').strip()
252*90c8c64dSAndroid Build Coastguard Worker            except subprocess.CalledProcessError as e:
253*90c8c64dSAndroid Build Coastguard Worker                print('Cannot resolve adb: ADB does not exist within path. '
254*90c8c64dSAndroid Build Coastguard Worker                      'Did you forget to setup the build environment or set '
255*90c8c64dSAndroid Build Coastguard Worker                      '--adb?',
256*90c8c64dSAndroid Build Coastguard Worker                      file=sys.stderr)
257*90c8c64dSAndroid Build Coastguard Worker                raise MissingResourceError(e)
258*90c8c64dSAndroid Build Coastguard Worker        # Start the adb server immediately so server daemon startup
259*90c8c64dSAndroid Build Coastguard Worker        # does not get added to the output of subsequent adb calls.
260*90c8c64dSAndroid Build Coastguard Worker        try:
261*90c8c64dSAndroid Build Coastguard Worker            get_output('%s start-server' % adb)
262*90c8c64dSAndroid Build Coastguard Worker            return Adb(adb)
263*90c8c64dSAndroid Build Coastguard Worker        except:
264*90c8c64dSAndroid Build Coastguard Worker            print('Unable to reach adb server daemon.', file=sys.stderr)
265*90c8c64dSAndroid Build Coastguard Worker            raise
266*90c8c64dSAndroid Build Coastguard Worker
267*90c8c64dSAndroid Build Coastguard Worker    @staticmethod
268*90c8c64dSAndroid Build Coastguard Worker    def _resolve_aapt(aapt_path):
269*90c8c64dSAndroid Build Coastguard Worker        """Resolves AAPT from either the cmdline argument or the os environment.
270*90c8c64dSAndroid Build Coastguard Worker
271*90c8c64dSAndroid Build Coastguard Worker        Returns:
272*90c8c64dSAndroid Build Coastguard Worker            An Aapt Object
273*90c8c64dSAndroid Build Coastguard Worker        """
274*90c8c64dSAndroid Build Coastguard Worker        if aapt_path:
275*90c8c64dSAndroid Build Coastguard Worker            if os.path.isfile(aapt_path):
276*90c8c64dSAndroid Build Coastguard Worker                return Aapt(aapt_path)
277*90c8c64dSAndroid Build Coastguard Worker            else:
278*90c8c64dSAndroid Build Coastguard Worker                raise MissingResourceError('Cannot resolve aapt: No such file '
279*90c8c64dSAndroid Build Coastguard Worker                                           '%s exists.' % aapt_path)
280*90c8c64dSAndroid Build Coastguard Worker        else:
281*90c8c64dSAndroid Build Coastguard Worker            try:
282*90c8c64dSAndroid Build Coastguard Worker                return Aapt(get_output('which aapt').strip())
283*90c8c64dSAndroid Build Coastguard Worker            except subprocess.CalledProcessError:
284*90c8c64dSAndroid Build Coastguard Worker                print('Cannot resolve aapt: AAPT does not exist within path. '
285*90c8c64dSAndroid Build Coastguard Worker                      'Did you forget to setup the build environment or set '
286*90c8c64dSAndroid Build Coastguard Worker                      '--aapt?',
287*90c8c64dSAndroid Build Coastguard Worker                      file=sys.stderr)
288*90c8c64dSAndroid Build Coastguard Worker                raise
289*90c8c64dSAndroid Build Coastguard Worker
290*90c8c64dSAndroid Build Coastguard Worker    def _resolve_serial(self, device, serial):
291*90c8c64dSAndroid Build Coastguard Worker        """Resolves the serial used for device files or generating permissions.
292*90c8c64dSAndroid Build Coastguard Worker
293*90c8c64dSAndroid Build Coastguard Worker        Returns:
294*90c8c64dSAndroid Build Coastguard Worker            If -s/--serial is specified, it will return that serial.
295*90c8c64dSAndroid Build Coastguard Worker            If -d or device: is found, it will grab the only available device.
296*90c8c64dSAndroid Build Coastguard Worker            If there are multiple devices, it will use $ANDROID_SERIAL.
297*90c8c64dSAndroid Build Coastguard Worker        Raises:
298*90c8c64dSAndroid Build Coastguard Worker            MissingResourceError if the resolved serial would not be usable.
299*90c8c64dSAndroid Build Coastguard Worker            subprocess.CalledProcessError if a command error occurs.
300*90c8c64dSAndroid Build Coastguard Worker        """
301*90c8c64dSAndroid Build Coastguard Worker        if device:
302*90c8c64dSAndroid Build Coastguard Worker            if serial:
303*90c8c64dSAndroid Build Coastguard Worker                try:
304*90c8c64dSAndroid Build Coastguard Worker                    output = get_output('%s -s %s get-state' %
305*90c8c64dSAndroid Build Coastguard Worker                                        (self.adb.path, serial))
306*90c8c64dSAndroid Build Coastguard Worker                except subprocess.CalledProcessError:
307*90c8c64dSAndroid Build Coastguard Worker                    raise MissingResourceError(
308*90c8c64dSAndroid Build Coastguard Worker                        'Received error when trying to get the state of '
309*90c8c64dSAndroid Build Coastguard Worker                        'device with serial "%s". Is it connected and in '
310*90c8c64dSAndroid Build Coastguard Worker                        'device mode?' % serial)
311*90c8c64dSAndroid Build Coastguard Worker                if 'device' not in output:
312*90c8c64dSAndroid Build Coastguard Worker                    raise MissingResourceError(
313*90c8c64dSAndroid Build Coastguard Worker                        'Device "%s" is not in device mode. Reboot the phone '
314*90c8c64dSAndroid Build Coastguard Worker                        'into device mode and try again.' % serial)
315*90c8c64dSAndroid Build Coastguard Worker                return serial
316*90c8c64dSAndroid Build Coastguard Worker
317*90c8c64dSAndroid Build Coastguard Worker            elif 'ANDROID_SERIAL' in os.environ:
318*90c8c64dSAndroid Build Coastguard Worker                serial = os.environ['ANDROID_SERIAL']
319*90c8c64dSAndroid Build Coastguard Worker                command = '%s -s %s get-state' % (self.adb, serial)
320*90c8c64dSAndroid Build Coastguard Worker                try:
321*90c8c64dSAndroid Build Coastguard Worker                    output = get_output(command)
322*90c8c64dSAndroid Build Coastguard Worker                except subprocess.CalledProcessError:
323*90c8c64dSAndroid Build Coastguard Worker                    raise MissingResourceError(
324*90c8c64dSAndroid Build Coastguard Worker                        'Device with serial $ANDROID_SERIAL ("%s") not '
325*90c8c64dSAndroid Build Coastguard Worker                        'found.' % serial)
326*90c8c64dSAndroid Build Coastguard Worker                if 'device' in output:
327*90c8c64dSAndroid Build Coastguard Worker                    return serial
328*90c8c64dSAndroid Build Coastguard Worker                raise MissingResourceError(
329*90c8c64dSAndroid Build Coastguard Worker                    'Device with serial $ANDROID_SERIAL ("%s") was '
330*90c8c64dSAndroid Build Coastguard Worker                    'found, but was not in the "device" state.')
331*90c8c64dSAndroid Build Coastguard Worker
332*90c8c64dSAndroid Build Coastguard Worker            # Parses `adb devices` so it only returns a string of serials.
333*90c8c64dSAndroid Build Coastguard Worker            get_serials_cmd = ('%s devices | tail -n +2 | head -n -1 | '
334*90c8c64dSAndroid Build Coastguard Worker                               'cut -f1' % self.adb.path)
335*90c8c64dSAndroid Build Coastguard Worker            try:
336*90c8c64dSAndroid Build Coastguard Worker                output = get_output(get_serials_cmd)
337*90c8c64dSAndroid Build Coastguard Worker                # If multiple serials appear in the output, raise an error.
338*90c8c64dSAndroid Build Coastguard Worker                if len(output.split()) > 1:
339*90c8c64dSAndroid Build Coastguard Worker                    raise MissingResourceError(
340*90c8c64dSAndroid Build Coastguard Worker                        'Multiple devices are connected. You must specify '
341*90c8c64dSAndroid Build Coastguard Worker                        'which device to run against with flag --serial.')
342*90c8c64dSAndroid Build Coastguard Worker                return output.strip()
343*90c8c64dSAndroid Build Coastguard Worker            except subprocess.CalledProcessError:
344*90c8c64dSAndroid Build Coastguard Worker                print('Unexpected error when querying for connected '
345*90c8c64dSAndroid Build Coastguard Worker                      'devices.', file=sys.stderr)
346*90c8c64dSAndroid Build Coastguard Worker                raise
347*90c8c64dSAndroid Build Coastguard Worker
348*90c8c64dSAndroid Build Coastguard Worker    def _get_partitions(self):
349*90c8c64dSAndroid Build Coastguard Worker        """Find all the partitions to examine
350*90c8c64dSAndroid Build Coastguard Worker
351*90c8c64dSAndroid Build Coastguard Worker        Returns:
352*90c8c64dSAndroid Build Coastguard Worker            The array of partitions where priv-app exists
353*90c8c64dSAndroid Build Coastguard Worker        Raises:
354*90c8c64dSAndroid Build Coastguard Worker            MissingResourceError find command over adb shell fails.
355*90c8c64dSAndroid Build Coastguard Worker        """
356*90c8c64dSAndroid Build Coastguard Worker        if not self.adb.serial:
357*90c8c64dSAndroid Build Coastguard Worker            privapp_dirs = get_output('cd  %s; find * -name "priv-app"'
358*90c8c64dSAndroid Build Coastguard Worker                                      % os.environ['ANDROID_PRODUCT_OUT']
359*90c8c64dSAndroid Build Coastguard Worker                                      + ' -type d | grep -v obj').split()
360*90c8c64dSAndroid Build Coastguard Worker        else:
361*90c8c64dSAndroid Build Coastguard Worker            try:
362*90c8c64dSAndroid Build Coastguard Worker                privapp_dirs = self.adb.call('shell find \'/!(proc)\' \
363*90c8c64dSAndroid Build Coastguard Worker                                           -name "priv-app" -type d').split()
364*90c8c64dSAndroid Build Coastguard Worker            except subprocess.CalledProcessError:
365*90c8c64dSAndroid Build Coastguard Worker                raise MissingResourceError(
366*90c8c64dSAndroid Build Coastguard Worker                    '"adb shell find / -name priv-app -type d" did not succeed'
367*90c8c64dSAndroid Build Coastguard Worker                    ' on device "%s".' % self.adb.serial)
368*90c8c64dSAndroid Build Coastguard Worker
369*90c8c64dSAndroid Build Coastguard Worker        # Remove 'priv-app' from the privapp_dirs
370*90c8c64dSAndroid Build Coastguard Worker        partitions = []
371*90c8c64dSAndroid Build Coastguard Worker        for i in range(len(privapp_dirs)):
372*90c8c64dSAndroid Build Coastguard Worker            partitions.append('/'.join(privapp_dirs[i].split('/')[:-1]))
373*90c8c64dSAndroid Build Coastguard Worker
374*90c8c64dSAndroid Build Coastguard Worker        return partitions
375*90c8c64dSAndroid Build Coastguard Worker
376*90c8c64dSAndroid Build Coastguard Worker    def _check_dir(self, directory):
377*90c8c64dSAndroid Build Coastguard Worker        """Check if a given directory is valid
378*90c8c64dSAndroid Build Coastguard Worker
379*90c8c64dSAndroid Build Coastguard Worker        Raises:
380*90c8c64dSAndroid Build Coastguard Worker            MissingResourceError if a given directory does not exist.
381*90c8c64dSAndroid Build Coastguard Worker        """
382*90c8c64dSAndroid Build Coastguard Worker        if not self.adb.serial:
383*90c8c64dSAndroid Build Coastguard Worker            if not os.path.isdir(os.environ['ANDROID_PRODUCT_OUT']
384*90c8c64dSAndroid Build Coastguard Worker                                 + '/' + directory):
385*90c8c64dSAndroid Build Coastguard Worker                raise MissingResourceError(
386*90c8c64dSAndroid Build Coastguard Worker                    '%s does not exist' % directory)
387*90c8c64dSAndroid Build Coastguard Worker        else:
388*90c8c64dSAndroid Build Coastguard Worker            try:
389*90c8c64dSAndroid Build Coastguard Worker                self.adb.call('shell ls %s' % directory)
390*90c8c64dSAndroid Build Coastguard Worker            except subprocess.CalledProcessError:
391*90c8c64dSAndroid Build Coastguard Worker                raise MissingResourceError(
392*90c8c64dSAndroid Build Coastguard Worker                    '"adb shell ls %s" did not succeed on '
393*90c8c64dSAndroid Build Coastguard Worker                    'device "%s".' % (directory, self.adb.serial))
394*90c8c64dSAndroid Build Coastguard Worker
395*90c8c64dSAndroid Build Coastguard Worker
396*90c8c64dSAndroid Build Coastguard Worker    def _resolve_apks(self, apks, partitions):
397*90c8c64dSAndroid Build Coastguard Worker        """Resolves all APKs to run against.
398*90c8c64dSAndroid Build Coastguard Worker
399*90c8c64dSAndroid Build Coastguard Worker        Returns:
400*90c8c64dSAndroid Build Coastguard Worker            If no apk is specified in the arguments, return all apks in
401*90c8c64dSAndroid Build Coastguard Worker            priv-app in all the partitions.
402*90c8c64dSAndroid Build Coastguard Worker            Otherwise, returns a list with the specified apk.
403*90c8c64dSAndroid Build Coastguard Worker        Throws:
404*90c8c64dSAndroid Build Coastguard Worker            MissingResourceError if the specified apk or
405*90c8c64dSAndroid Build Coastguard Worker            <partition>/priv-app cannot be found.
406*90c8c64dSAndroid Build Coastguard Worker        """
407*90c8c64dSAndroid Build Coastguard Worker        results = {}
408*90c8c64dSAndroid Build Coastguard Worker        if not apks:
409*90c8c64dSAndroid Build Coastguard Worker            for p in partitions:
410*90c8c64dSAndroid Build Coastguard Worker                results[p] = self._resolve_all_privapps(p)
411*90c8c64dSAndroid Build Coastguard Worker            return results
412*90c8c64dSAndroid Build Coastguard Worker
413*90c8c64dSAndroid Build Coastguard Worker        # The first element is what is passed via '-p' option
414*90c8c64dSAndroid Build Coastguard Worker        # (default is overwritten to 'system' when apk is specified)
415*90c8c64dSAndroid Build Coastguard Worker        p = partitions[0]
416*90c8c64dSAndroid Build Coastguard Worker        results[p] = []
417*90c8c64dSAndroid Build Coastguard Worker        for apk in apks:
418*90c8c64dSAndroid Build Coastguard Worker            if apk.startswith(DEVICE_PREFIX):
419*90c8c64dSAndroid Build Coastguard Worker                device_apk = apk[len(DEVICE_PREFIX):]
420*90c8c64dSAndroid Build Coastguard Worker                try:
421*90c8c64dSAndroid Build Coastguard Worker                    apk = self.adb.pull(device_apk)
422*90c8c64dSAndroid Build Coastguard Worker                except subprocess.CalledProcessError:
423*90c8c64dSAndroid Build Coastguard Worker                    raise MissingResourceError(
424*90c8c64dSAndroid Build Coastguard Worker                        'File "%s" could not be located on device "%s".' %
425*90c8c64dSAndroid Build Coastguard Worker                        (device_apk, self.adb.serial))
426*90c8c64dSAndroid Build Coastguard Worker                results[p].append(apk)
427*90c8c64dSAndroid Build Coastguard Worker            elif not os.path.isfile(apk):
428*90c8c64dSAndroid Build Coastguard Worker                raise MissingResourceError('File "%s" does not exist.' % apk)
429*90c8c64dSAndroid Build Coastguard Worker            else:
430*90c8c64dSAndroid Build Coastguard Worker               results[p].append(apk)
431*90c8c64dSAndroid Build Coastguard Worker        return results
432*90c8c64dSAndroid Build Coastguard Worker
433*90c8c64dSAndroid Build Coastguard Worker    def _resolve_all_privapps(self, partition):
434*90c8c64dSAndroid Build Coastguard Worker        """Resolves all APKs in <partition>/priv-app
435*90c8c64dSAndroid Build Coastguard Worker
436*90c8c64dSAndroid Build Coastguard Worker        Returns:
437*90c8c64dSAndroid Build Coastguard Worker            Return all apks in <partition>/priv-app
438*90c8c64dSAndroid Build Coastguard Worker        Throws:
439*90c8c64dSAndroid Build Coastguard Worker            MissingResourceError <partition>/priv-app cannot be found.
440*90c8c64dSAndroid Build Coastguard Worker        """
441*90c8c64dSAndroid Build Coastguard Worker        if not self.adb.serial:
442*90c8c64dSAndroid Build Coastguard Worker            priv_app_dir = os.path.join(os.environ['ANDROID_PRODUCT_OUT'],
443*90c8c64dSAndroid Build Coastguard Worker                                        partition + '/priv-app')
444*90c8c64dSAndroid Build Coastguard Worker        else:
445*90c8c64dSAndroid Build Coastguard Worker            try:
446*90c8c64dSAndroid Build Coastguard Worker                priv_app_dir = self.adb.pull(partition + '/priv-app/')
447*90c8c64dSAndroid Build Coastguard Worker            except subprocess.CalledProcessError:
448*90c8c64dSAndroid Build Coastguard Worker                raise MissingResourceError(
449*90c8c64dSAndroid Build Coastguard Worker                    'Directory "%s/priv-app" could not be pulled from on '
450*90c8c64dSAndroid Build Coastguard Worker                    'device "%s".' % (partition, self.adb.serial))
451*90c8c64dSAndroid Build Coastguard Worker        return get_output('find %s -name "*.apk"' % priv_app_dir).split()
452*90c8c64dSAndroid Build Coastguard Worker
453*90c8c64dSAndroid Build Coastguard Worker    def _resolve_sys_path(self, file_path):
454*90c8c64dSAndroid Build Coastguard Worker        """Resolves a path that is a part of an Android System Image."""
455*90c8c64dSAndroid Build Coastguard Worker        if not self.adb.serial:
456*90c8c64dSAndroid Build Coastguard Worker            return os.path.join(os.environ['ANDROID_PRODUCT_OUT'], file_path)
457*90c8c64dSAndroid Build Coastguard Worker        else:
458*90c8c64dSAndroid Build Coastguard Worker            return self.adb.pull(file_path)
459*90c8c64dSAndroid Build Coastguard Worker
460*90c8c64dSAndroid Build Coastguard Worker    def _resolve_sys_paths(self, file_path, partitions):
461*90c8c64dSAndroid Build Coastguard Worker        """Resolves a path that is a part of an Android System Image, for the
462*90c8c64dSAndroid Build Coastguard Worker        specified partitions."""
463*90c8c64dSAndroid Build Coastguard Worker        results = {}
464*90c8c64dSAndroid Build Coastguard Worker        for p in partitions:
465*90c8c64dSAndroid Build Coastguard Worker            results[p] = self._resolve_sys_path(p + '/' + file_path)
466*90c8c64dSAndroid Build Coastguard Worker        return results
467*90c8c64dSAndroid Build Coastguard Worker
468*90c8c64dSAndroid Build Coastguard Worker
469*90c8c64dSAndroid Build Coastguard Workerdef get_output(command):
470*90c8c64dSAndroid Build Coastguard Worker    """Returns the output of the command as a string.
471*90c8c64dSAndroid Build Coastguard Worker
472*90c8c64dSAndroid Build Coastguard Worker    Throws:
473*90c8c64dSAndroid Build Coastguard Worker        subprocess.CalledProcessError if exit status is non-zero.
474*90c8c64dSAndroid Build Coastguard Worker    """
475*90c8c64dSAndroid Build Coastguard Worker    output = subprocess.check_output(command, shell=True)
476*90c8c64dSAndroid Build Coastguard Worker    # For Python3.4, decode the byte string so it is usable.
477*90c8c64dSAndroid Build Coastguard Worker    return output.decode(encoding='UTF-8')
478*90c8c64dSAndroid Build Coastguard Worker
479*90c8c64dSAndroid Build Coastguard Worker
480*90c8c64dSAndroid Build Coastguard Workerdef parse_args():
481*90c8c64dSAndroid Build Coastguard Worker    """Parses the CLI."""
482*90c8c64dSAndroid Build Coastguard Worker    parser = argparse.ArgumentParser(
483*90c8c64dSAndroid Build Coastguard Worker        description=HELP_MESSAGE,
484*90c8c64dSAndroid Build Coastguard Worker        formatter_class=argparse.RawDescriptionHelpFormatter)
485*90c8c64dSAndroid Build Coastguard Worker    parser.add_argument(
486*90c8c64dSAndroid Build Coastguard Worker        '-d',
487*90c8c64dSAndroid Build Coastguard Worker        '--device',
488*90c8c64dSAndroid Build Coastguard Worker        action='store_true',
489*90c8c64dSAndroid Build Coastguard Worker        default=False,
490*90c8c64dSAndroid Build Coastguard Worker        required=False,
491*90c8c64dSAndroid Build Coastguard Worker        help='Whether or not to generate the privapp_permissions file for the '
492*90c8c64dSAndroid Build Coastguard Worker             'build already on a device. See -s/--serial below for more '
493*90c8c64dSAndroid Build Coastguard Worker             'details.'
494*90c8c64dSAndroid Build Coastguard Worker    )
495*90c8c64dSAndroid Build Coastguard Worker    parser.add_argument(
496*90c8c64dSAndroid Build Coastguard Worker        '-v',
497*90c8c64dSAndroid Build Coastguard Worker        '--verbose',
498*90c8c64dSAndroid Build Coastguard Worker        action='store_true',
499*90c8c64dSAndroid Build Coastguard Worker        default=False,
500*90c8c64dSAndroid Build Coastguard Worker        required=False,
501*90c8c64dSAndroid Build Coastguard Worker        help='Whether or not to enable more verbose logs such as '
502*90c8c64dSAndroid Build Coastguard Worker             'adb pull progress to be shown'
503*90c8c64dSAndroid Build Coastguard Worker    )
504*90c8c64dSAndroid Build Coastguard Worker    parser.add_argument(
505*90c8c64dSAndroid Build Coastguard Worker        '--adb',
506*90c8c64dSAndroid Build Coastguard Worker        type=str,
507*90c8c64dSAndroid Build Coastguard Worker        required=False,
508*90c8c64dSAndroid Build Coastguard Worker        metavar='<ADB_PATH>',
509*90c8c64dSAndroid Build Coastguard Worker        help='Path to adb. If none specified, uses the environment\'s adb.'
510*90c8c64dSAndroid Build Coastguard Worker    )
511*90c8c64dSAndroid Build Coastguard Worker    parser.add_argument(
512*90c8c64dSAndroid Build Coastguard Worker        '--aapt',
513*90c8c64dSAndroid Build Coastguard Worker        type=str,
514*90c8c64dSAndroid Build Coastguard Worker        required=False,
515*90c8c64dSAndroid Build Coastguard Worker        metavar='<AAPT_PATH>',
516*90c8c64dSAndroid Build Coastguard Worker        help='Path to aapt. If none specified, uses the environment\'s aapt.'
517*90c8c64dSAndroid Build Coastguard Worker    )
518*90c8c64dSAndroid Build Coastguard Worker    parser.add_argument(
519*90c8c64dSAndroid Build Coastguard Worker        '-s',
520*90c8c64dSAndroid Build Coastguard Worker        '--serial',
521*90c8c64dSAndroid Build Coastguard Worker        type=str,
522*90c8c64dSAndroid Build Coastguard Worker        required=False,
523*90c8c64dSAndroid Build Coastguard Worker        metavar='<SERIAL>',
524*90c8c64dSAndroid Build Coastguard Worker        help='The serial of the device to generate permissions for. If no '
525*90c8c64dSAndroid Build Coastguard Worker             'serial is given, it will pick the only device connected over '
526*90c8c64dSAndroid Build Coastguard Worker             'adb. If multiple devices are found, it will default to '
527*90c8c64dSAndroid Build Coastguard Worker             '$ANDROID_SERIAL. Otherwise, the program will exit with error '
528*90c8c64dSAndroid Build Coastguard Worker             'code 1. If -s is given, -d is not needed.'
529*90c8c64dSAndroid Build Coastguard Worker    )
530*90c8c64dSAndroid Build Coastguard Worker    parser.add_argument(
531*90c8c64dSAndroid Build Coastguard Worker        '-p',
532*90c8c64dSAndroid Build Coastguard Worker        '--partitions',
533*90c8c64dSAndroid Build Coastguard Worker        type=str,
534*90c8c64dSAndroid Build Coastguard Worker        required=False,
535*90c8c64dSAndroid Build Coastguard Worker        default='all',
536*90c8c64dSAndroid Build Coastguard Worker        metavar='<PARTITION>',
537*90c8c64dSAndroid Build Coastguard Worker        help='The target partition(s) to examine permissions for. '
538*90c8c64dSAndroid Build Coastguard Worker             'It is set to "all" by default, which means all the partitions '
539*90c8c64dSAndroid Build Coastguard Worker             'where priv-app diectory exists will be examined'
540*90c8c64dSAndroid Build Coastguard Worker             'Use "," as a delimiter when specifying multiple partitions. '
541*90c8c64dSAndroid Build Coastguard Worker             'E.g. "system,product"'
542*90c8c64dSAndroid Build Coastguard Worker    )
543*90c8c64dSAndroid Build Coastguard Worker    parser.add_argument(
544*90c8c64dSAndroid Build Coastguard Worker        'apks',
545*90c8c64dSAndroid Build Coastguard Worker        nargs='*',
546*90c8c64dSAndroid Build Coastguard Worker        type=str,
547*90c8c64dSAndroid Build Coastguard Worker        help='A list of paths to priv-app APKs to generate permissions for. '
548*90c8c64dSAndroid Build Coastguard Worker             'To make a path device-side, prefix the path with "device:".'
549*90c8c64dSAndroid Build Coastguard Worker    )
550*90c8c64dSAndroid Build Coastguard Worker    parser.add_argument(
551*90c8c64dSAndroid Build Coastguard Worker        '-w',
552*90c8c64dSAndroid Build Coastguard Worker        '--writetodisk',
553*90c8c64dSAndroid Build Coastguard Worker        action='store_true',
554*90c8c64dSAndroid Build Coastguard Worker        default=False,
555*90c8c64dSAndroid Build Coastguard Worker        required=False,
556*90c8c64dSAndroid Build Coastguard Worker        help='Whether or not to store the generated permissions directly to '
557*90c8c64dSAndroid Build Coastguard Worker             'a file. See --systemfile/--productfile for more information.'
558*90c8c64dSAndroid Build Coastguard Worker    )
559*90c8c64dSAndroid Build Coastguard Worker    parser.add_argument(
560*90c8c64dSAndroid Build Coastguard Worker        '--systemfile',
561*90c8c64dSAndroid Build Coastguard Worker        default='./system.xml',
562*90c8c64dSAndroid Build Coastguard Worker        required=False,
563*90c8c64dSAndroid Build Coastguard Worker        help='Path to system permissions file. Default value is ./system.xml'
564*90c8c64dSAndroid Build Coastguard Worker    )
565*90c8c64dSAndroid Build Coastguard Worker    parser.add_argument(
566*90c8c64dSAndroid Build Coastguard Worker        '--productfile',
567*90c8c64dSAndroid Build Coastguard Worker        default='./product.xml',
568*90c8c64dSAndroid Build Coastguard Worker        required=False,
569*90c8c64dSAndroid Build Coastguard Worker        help='Path to system permissions file. Default value is ./product.xml'
570*90c8c64dSAndroid Build Coastguard Worker    )
571*90c8c64dSAndroid Build Coastguard Worker    cmd_args = parser.parse_args()
572*90c8c64dSAndroid Build Coastguard Worker
573*90c8c64dSAndroid Build Coastguard Worker    return cmd_args
574*90c8c64dSAndroid Build Coastguard Worker
575*90c8c64dSAndroid Build Coastguard Worker
576*90c8c64dSAndroid Build Coastguard Workerdef create_permission_file(resources):
577*90c8c64dSAndroid Build Coastguard Worker    """Prints out/creates permission file with missing permissions."""
578*90c8c64dSAndroid Build Coastguard Worker    # First extract privileged permissions from framework-res.apk
579*90c8c64dSAndroid Build Coastguard Worker    priv_permissions = extract_priv_permissions(resources.aapt,
580*90c8c64dSAndroid Build Coastguard Worker                                                resources.framework_res_apk)
581*90c8c64dSAndroid Build Coastguard Worker
582*90c8c64dSAndroid Build Coastguard Worker    results = {}
583*90c8c64dSAndroid Build Coastguard Worker    for p in resources.partitions:
584*90c8c64dSAndroid Build Coastguard Worker        results[p], apps_redefine_base = \
585*90c8c64dSAndroid Build Coastguard Worker            generate_missing_permissions(resources, priv_permissions, p)
586*90c8c64dSAndroid Build Coastguard Worker        enable_print = True
587*90c8c64dSAndroid Build Coastguard Worker        vprint(enable_print, '#' * 80)
588*90c8c64dSAndroid Build Coastguard Worker        vprint(enable_print, '#')
589*90c8c64dSAndroid Build Coastguard Worker        if resources.writetodisk:
590*90c8c64dSAndroid Build Coastguard Worker            # Check if it is likely a product partition
591*90c8c64dSAndroid Build Coastguard Worker            if p.endswith('product'):
592*90c8c64dSAndroid Build Coastguard Worker                out_file_name = resources.productfile;
593*90c8c64dSAndroid Build Coastguard Worker            # Check if it is a system partition
594*90c8c64dSAndroid Build Coastguard Worker            elif p.endswith('system'):
595*90c8c64dSAndroid Build Coastguard Worker                out_file_name = resources.systemfile
596*90c8c64dSAndroid Build Coastguard Worker            # Fallback to the partition name itself
597*90c8c64dSAndroid Build Coastguard Worker            else:
598*90c8c64dSAndroid Build Coastguard Worker                out_file_name = str(p).replace('/', '_') + '.xml'
599*90c8c64dSAndroid Build Coastguard Worker
600*90c8c64dSAndroid Build Coastguard Worker            out_file = open(out_file_name, 'w')
601*90c8c64dSAndroid Build Coastguard Worker            vprint(enable_print, '# %s XML written to %s:', p, out_file_name)
602*90c8c64dSAndroid Build Coastguard Worker            vprint(enable_print, '#')
603*90c8c64dSAndroid Build Coastguard Worker            vprint(enable_print, '#' * 80)
604*90c8c64dSAndroid Build Coastguard Worker            print_xml(results[p], apps_redefine_base, p, out_file)
605*90c8c64dSAndroid Build Coastguard Worker            out_file.close()
606*90c8c64dSAndroid Build Coastguard Worker        else:
607*90c8c64dSAndroid Build Coastguard Worker            vprint(enable_print, '# %s XML:', p)
608*90c8c64dSAndroid Build Coastguard Worker            vprint(enable_print, '#')
609*90c8c64dSAndroid Build Coastguard Worker            vprint(enable_print, '#' * 80)
610*90c8c64dSAndroid Build Coastguard Worker
611*90c8c64dSAndroid Build Coastguard Worker        # Print it to stdout regardless of whether writing to a file or not
612*90c8c64dSAndroid Build Coastguard Worker        print_xml(results[p], apps_redefine_base, p)
613*90c8c64dSAndroid Build Coastguard Worker
614*90c8c64dSAndroid Build Coastguard Worker
615*90c8c64dSAndroid Build Coastguard Workerdef generate_missing_permissions(resources, priv_permissions, partition):
616*90c8c64dSAndroid Build Coastguard Worker    """Generates the missing permissions for the specified partition."""
617*90c8c64dSAndroid Build Coastguard Worker    # Parse base XML files in /etc dir, permissions listed there don't have
618*90c8c64dSAndroid Build Coastguard Worker    # to be re-added
619*90c8c64dSAndroid Build Coastguard Worker    base_permissions = {}
620*90c8c64dSAndroid Build Coastguard Worker    base_xml_files = itertools.chain(
621*90c8c64dSAndroid Build Coastguard Worker        list_xml_files(resources.permissions_dirs[partition]),
622*90c8c64dSAndroid Build Coastguard Worker        list_xml_files(resources.sysconfig_dirs[partition]))
623*90c8c64dSAndroid Build Coastguard Worker
624*90c8c64dSAndroid Build Coastguard Worker    for xml_file in base_xml_files:
625*90c8c64dSAndroid Build Coastguard Worker        parse_config_xml(xml_file, base_permissions)
626*90c8c64dSAndroid Build Coastguard Worker
627*90c8c64dSAndroid Build Coastguard Worker    apps_redefine_base = []
628*90c8c64dSAndroid Build Coastguard Worker    results = {}
629*90c8c64dSAndroid Build Coastguard Worker    for priv_app in resources.privapp_apks[partition]:
630*90c8c64dSAndroid Build Coastguard Worker        pkg_info = extract_pkg_and_requested_permissions(resources.aapt,
631*90c8c64dSAndroid Build Coastguard Worker                                                         priv_app)
632*90c8c64dSAndroid Build Coastguard Worker        pkg_name = pkg_info['package_name']
633*90c8c64dSAndroid Build Coastguard Worker        # get intersection of what's requested by app and by framework
634*90c8c64dSAndroid Build Coastguard Worker        priv_perms = get_priv_permissions(pkg_info['permissions'],
635*90c8c64dSAndroid Build Coastguard Worker                                          priv_permissions)
636*90c8c64dSAndroid Build Coastguard Worker        # Compute diff against permissions defined in base file
637*90c8c64dSAndroid Build Coastguard Worker        if base_permissions and (pkg_name in base_permissions):
638*90c8c64dSAndroid Build Coastguard Worker            base_permissions_pkg = base_permissions[pkg_name]
639*90c8c64dSAndroid Build Coastguard Worker            priv_perms = remove_base_permissions(priv_perms,
640*90c8c64dSAndroid Build Coastguard Worker                                                 base_permissions_pkg)
641*90c8c64dSAndroid Build Coastguard Worker            if priv_perms:
642*90c8c64dSAndroid Build Coastguard Worker                apps_redefine_base.append(pkg_name)
643*90c8c64dSAndroid Build Coastguard Worker        if priv_perms:
644*90c8c64dSAndroid Build Coastguard Worker            results[pkg_name] = sorted(priv_perms)
645*90c8c64dSAndroid Build Coastguard Worker
646*90c8c64dSAndroid Build Coastguard Worker    return results, apps_redefine_base
647*90c8c64dSAndroid Build Coastguard Worker
648*90c8c64dSAndroid Build Coastguard Worker
649*90c8c64dSAndroid Build Coastguard Workerdef print_xml(results, apps_redefine_base, partition, fd=sys.stdout):
650*90c8c64dSAndroid Build Coastguard Worker    """Print results to the given file."""
651*90c8c64dSAndroid Build Coastguard Worker    fd.write('<?xml version="1.0" encoding="utf-8"?>\n')
652*90c8c64dSAndroid Build Coastguard Worker    fd.write('<!-- for the partition: /%s -->\n' % partition)
653*90c8c64dSAndroid Build Coastguard Worker    fd.write('<permissions>\n')
654*90c8c64dSAndroid Build Coastguard Worker    for package_name in sorted(results):
655*90c8c64dSAndroid Build Coastguard Worker        if package_name in apps_redefine_base:
656*90c8c64dSAndroid Build Coastguard Worker            fd.write('    <!-- Additional permissions on top of %s -->\n' %
657*90c8c64dSAndroid Build Coastguard Worker                     BASE_XML_FILENAME)
658*90c8c64dSAndroid Build Coastguard Worker        fd.write('    <privapp-permissions package="%s">\n' % package_name)
659*90c8c64dSAndroid Build Coastguard Worker        for p in results[package_name]:
660*90c8c64dSAndroid Build Coastguard Worker            fd.write('        <permission name="%s"/>\n' % p)
661*90c8c64dSAndroid Build Coastguard Worker        fd.write('    </privapp-permissions>\n')
662*90c8c64dSAndroid Build Coastguard Worker        fd.write('\n')
663*90c8c64dSAndroid Build Coastguard Worker
664*90c8c64dSAndroid Build Coastguard Worker    fd.write('</permissions>\n')
665*90c8c64dSAndroid Build Coastguard Worker
666*90c8c64dSAndroid Build Coastguard Worker
667*90c8c64dSAndroid Build Coastguard Workerdef remove_base_permissions(priv_perms, base_perms):
668*90c8c64dSAndroid Build Coastguard Worker    """Removes set of base_perms from set of priv_perms."""
669*90c8c64dSAndroid Build Coastguard Worker    if (not priv_perms) or (not base_perms):
670*90c8c64dSAndroid Build Coastguard Worker        return priv_perms
671*90c8c64dSAndroid Build Coastguard Worker    return set(priv_perms) - set(base_perms)
672*90c8c64dSAndroid Build Coastguard Worker
673*90c8c64dSAndroid Build Coastguard Worker
674*90c8c64dSAndroid Build Coastguard Workerdef get_priv_permissions(requested_perms, priv_perms):
675*90c8c64dSAndroid Build Coastguard Worker    """Return only permissions that are in priv_perms set."""
676*90c8c64dSAndroid Build Coastguard Worker    return set(requested_perms).intersection(set(priv_perms))
677*90c8c64dSAndroid Build Coastguard Worker
678*90c8c64dSAndroid Build Coastguard Worker
679*90c8c64dSAndroid Build Coastguard Workerdef list_xml_files(directory):
680*90c8c64dSAndroid Build Coastguard Worker    """Returns a list of all .xml files within a given directory.
681*90c8c64dSAndroid Build Coastguard Worker
682*90c8c64dSAndroid Build Coastguard Worker    Args:
683*90c8c64dSAndroid Build Coastguard Worker        directory: the directory to look for xml files in.
684*90c8c64dSAndroid Build Coastguard Worker    """
685*90c8c64dSAndroid Build Coastguard Worker    xml_files = []
686*90c8c64dSAndroid Build Coastguard Worker    for dirName, subdirList, file_list in os.walk(directory):
687*90c8c64dSAndroid Build Coastguard Worker        for file in file_list:
688*90c8c64dSAndroid Build Coastguard Worker            if file.endswith('.xml'):
689*90c8c64dSAndroid Build Coastguard Worker                file_path = os.path.join(dirName, file)
690*90c8c64dSAndroid Build Coastguard Worker                xml_files.append(file_path)
691*90c8c64dSAndroid Build Coastguard Worker    return xml_files
692*90c8c64dSAndroid Build Coastguard Worker
693*90c8c64dSAndroid Build Coastguard Worker
694*90c8c64dSAndroid Build Coastguard Workerdef extract_pkg_and_requested_permissions(aapt, apk_path):
695*90c8c64dSAndroid Build Coastguard Worker    """
696*90c8c64dSAndroid Build Coastguard Worker    Extract package name and list of requested permissions from the
697*90c8c64dSAndroid Build Coastguard Worker    dump of manifest file
698*90c8c64dSAndroid Build Coastguard Worker    """
699*90c8c64dSAndroid Build Coastguard Worker    aapt_args = ['d', 'permissions', apk_path]
700*90c8c64dSAndroid Build Coastguard Worker    txt = aapt.call(aapt_args)
701*90c8c64dSAndroid Build Coastguard Worker
702*90c8c64dSAndroid Build Coastguard Worker    permissions = []
703*90c8c64dSAndroid Build Coastguard Worker    package_name = None
704*90c8c64dSAndroid Build Coastguard Worker    raw_lines = txt.split('\n')
705*90c8c64dSAndroid Build Coastguard Worker    for line in raw_lines:
706*90c8c64dSAndroid Build Coastguard Worker        regex = r"uses-permission.*: name='([\S]+)'"
707*90c8c64dSAndroid Build Coastguard Worker        matches = re.search(regex, line)
708*90c8c64dSAndroid Build Coastguard Worker        if matches:
709*90c8c64dSAndroid Build Coastguard Worker            name = matches.group(1)
710*90c8c64dSAndroid Build Coastguard Worker            permissions.append(name)
711*90c8c64dSAndroid Build Coastguard Worker        regex = r'package: ([\S]+)'
712*90c8c64dSAndroid Build Coastguard Worker        matches = re.search(regex, line)
713*90c8c64dSAndroid Build Coastguard Worker        if matches:
714*90c8c64dSAndroid Build Coastguard Worker            package_name = matches.group(1)
715*90c8c64dSAndroid Build Coastguard Worker
716*90c8c64dSAndroid Build Coastguard Worker    return {'package_name': package_name, 'permissions': permissions}
717*90c8c64dSAndroid Build Coastguard Worker
718*90c8c64dSAndroid Build Coastguard Worker
719*90c8c64dSAndroid Build Coastguard Workerdef extract_priv_permissions(aapt, apk_path):
720*90c8c64dSAndroid Build Coastguard Worker    """Extract signature|privileged permissions from dump of manifest file."""
721*90c8c64dSAndroid Build Coastguard Worker    aapt_args = ['d', 'xmltree', apk_path, 'AndroidManifest.xml']
722*90c8c64dSAndroid Build Coastguard Worker    txt = aapt.call(aapt_args)
723*90c8c64dSAndroid Build Coastguard Worker    raw_lines = txt.split('\n')
724*90c8c64dSAndroid Build Coastguard Worker    n = len(raw_lines)
725*90c8c64dSAndroid Build Coastguard Worker    i = 0
726*90c8c64dSAndroid Build Coastguard Worker    permissions_list = []
727*90c8c64dSAndroid Build Coastguard Worker    while i < n:
728*90c8c64dSAndroid Build Coastguard Worker        line = raw_lines[i]
729*90c8c64dSAndroid Build Coastguard Worker        if line.find('E: permission (') != -1:
730*90c8c64dSAndroid Build Coastguard Worker            i += 1
731*90c8c64dSAndroid Build Coastguard Worker            name = None
732*90c8c64dSAndroid Build Coastguard Worker            level = None
733*90c8c64dSAndroid Build Coastguard Worker            while i < n:
734*90c8c64dSAndroid Build Coastguard Worker                line = raw_lines[i]
735*90c8c64dSAndroid Build Coastguard Worker                if line.find('E: ') != -1:
736*90c8c64dSAndroid Build Coastguard Worker                    break
737*90c8c64dSAndroid Build Coastguard Worker                matches = re.search(ANDROID_NAME_REGEX, line)
738*90c8c64dSAndroid Build Coastguard Worker                if matches:
739*90c8c64dSAndroid Build Coastguard Worker                    name = matches.group(1)
740*90c8c64dSAndroid Build Coastguard Worker                    i += 1
741*90c8c64dSAndroid Build Coastguard Worker                    continue
742*90c8c64dSAndroid Build Coastguard Worker                matches = re.search(ANDROID_PROTECTION_LEVEL_REGEX, line)
743*90c8c64dSAndroid Build Coastguard Worker                if matches:
744*90c8c64dSAndroid Build Coastguard Worker                    level = int(matches.group(1), 16)
745*90c8c64dSAndroid Build Coastguard Worker                    i += 1
746*90c8c64dSAndroid Build Coastguard Worker                    continue
747*90c8c64dSAndroid Build Coastguard Worker                i += 1
748*90c8c64dSAndroid Build Coastguard Worker            if name and level and level & 0x12 == 0x12:
749*90c8c64dSAndroid Build Coastguard Worker                permissions_list.append(name)
750*90c8c64dSAndroid Build Coastguard Worker        else:
751*90c8c64dSAndroid Build Coastguard Worker            i += 1
752*90c8c64dSAndroid Build Coastguard Worker
753*90c8c64dSAndroid Build Coastguard Worker    return permissions_list
754*90c8c64dSAndroid Build Coastguard Worker
755*90c8c64dSAndroid Build Coastguard Worker
756*90c8c64dSAndroid Build Coastguard Workerdef parse_config_xml(base_xml, results):
757*90c8c64dSAndroid Build Coastguard Worker    """Parse an XML file that will be used as base."""
758*90c8c64dSAndroid Build Coastguard Worker    dom = minidom.parse(base_xml)
759*90c8c64dSAndroid Build Coastguard Worker    nodes = dom.getElementsByTagName('privapp-permissions')
760*90c8c64dSAndroid Build Coastguard Worker    for node in nodes:
761*90c8c64dSAndroid Build Coastguard Worker        permissions = (node.getElementsByTagName('permission') +
762*90c8c64dSAndroid Build Coastguard Worker                       node.getElementsByTagName('deny-permission'))
763*90c8c64dSAndroid Build Coastguard Worker        package_name = node.getAttribute('package')
764*90c8c64dSAndroid Build Coastguard Worker        plist = []
765*90c8c64dSAndroid Build Coastguard Worker        if package_name in results:
766*90c8c64dSAndroid Build Coastguard Worker            plist = results[package_name]
767*90c8c64dSAndroid Build Coastguard Worker        for p in permissions:
768*90c8c64dSAndroid Build Coastguard Worker            perm_name = p.getAttribute('name')
769*90c8c64dSAndroid Build Coastguard Worker            if perm_name:
770*90c8c64dSAndroid Build Coastguard Worker                plist.append(perm_name)
771*90c8c64dSAndroid Build Coastguard Worker        results[package_name] = plist
772*90c8c64dSAndroid Build Coastguard Worker    return results
773*90c8c64dSAndroid Build Coastguard Worker
774*90c8c64dSAndroid Build Coastguard Worker
775*90c8c64dSAndroid Build Coastguard Workerdef cleanup():
776*90c8c64dSAndroid Build Coastguard Worker    """Cleans up temp files."""
777*90c8c64dSAndroid Build Coastguard Worker    for directory in temp_dirs:
778*90c8c64dSAndroid Build Coastguard Worker        shutil.rmtree(directory, ignore_errors=True)
779*90c8c64dSAndroid Build Coastguard Worker    for file in temp_files:
780*90c8c64dSAndroid Build Coastguard Worker        os.remove(file)
781*90c8c64dSAndroid Build Coastguard Worker    del temp_dirs[:]
782*90c8c64dSAndroid Build Coastguard Worker    del temp_files[:]
783*90c8c64dSAndroid Build Coastguard Worker
784*90c8c64dSAndroid Build Coastguard Workerif __name__ == '__main__':
785*90c8c64dSAndroid Build Coastguard Worker    args = parse_args()
786*90c8c64dSAndroid Build Coastguard Worker    try:
787*90c8c64dSAndroid Build Coastguard Worker        tool_resources = Resources(
788*90c8c64dSAndroid Build Coastguard Worker            aapt_path=args.aapt,
789*90c8c64dSAndroid Build Coastguard Worker            adb_path=args.adb,
790*90c8c64dSAndroid Build Coastguard Worker            use_device=args.device,
791*90c8c64dSAndroid Build Coastguard Worker            serial=args.serial,
792*90c8c64dSAndroid Build Coastguard Worker            partitions=args.partitions,
793*90c8c64dSAndroid Build Coastguard Worker            verbose=args.verbose,
794*90c8c64dSAndroid Build Coastguard Worker            writetodisk=args.writetodisk,
795*90c8c64dSAndroid Build Coastguard Worker            systemfile=args.systemfile,
796*90c8c64dSAndroid Build Coastguard Worker            productfile=args.productfile,
797*90c8c64dSAndroid Build Coastguard Worker            apks=args.apks
798*90c8c64dSAndroid Build Coastguard Worker        )
799*90c8c64dSAndroid Build Coastguard Worker        create_permission_file(tool_resources)
800*90c8c64dSAndroid Build Coastguard Worker    except MissingResourceError as e:
801*90c8c64dSAndroid Build Coastguard Worker        print(str(e), file=sys.stderr)
802*90c8c64dSAndroid Build Coastguard Worker        exit(1)
803*90c8c64dSAndroid Build Coastguard Worker    finally:
804*90c8c64dSAndroid Build Coastguard Worker        cleanup()
805