1#!/usr/bin/env python3
2#
3#   Copyright 2019 - 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
17import os
18import re
19
20from acts.libs.proc import job
21
22PKG_NAME_PATTERN = r"^package:\s+name='(?P<pkg_name>.*?)'"
23PM_PATH_PATTERN = r"^package:(?P<apk_path>.*)"
24
25
26class AppInstaller(object):
27    """Class that represents an app on an Android device. Includes methods
28    for install, uninstall, and getting info.
29    """
30    def __init__(self, ad, apk_path):
31        """Initializes an AppInstaller.
32
33        Args:
34            ad: device to install the apk
35            apk_path: path to the apk
36        """
37        self._ad = ad
38        self._apk_path = apk_path
39        self._pkg_name = None
40
41    @staticmethod
42    def pull_from_device(ad, pkg_name, dest):
43        """Initializes an AppInstaller by pulling the apk file from the device,
44        given the package name
45
46        Args:
47            ad: device on which the apk is installed
48            pkg_name: package name
49            dest: destination directory
50                (Note: If path represents a directory, it must already exist as
51                 a directory)
52
53        Returns: AppInstaller object representing the pulled apk, or None if
54            package not installed
55        """
56        if not ad.is_apk_installed(pkg_name):
57            ad.log.warning('Unable to find package %s on device. Pull aborted.'
58                           % pkg_name)
59            return None
60        path_on_device = re.compile(PM_PATH_PATTERN).search(
61            ad.adb.shell('pm path %s' % pkg_name)).group('apk_path')
62        ad.pull_files(path_on_device, dest)
63        if os.path.isdir(dest):
64            dest = os.path.join(dest, os.path.basename(path_on_device))
65        return AppInstaller(ad, dest)
66
67    @property
68    def apk_path(self):
69        return self._apk_path
70
71    @property
72    def pkg_name(self):
73        """Get the package name corresponding to the apk from aapt
74
75        Returns: The package name, or empty string if not found.
76        """
77        if self._pkg_name is None:
78            dump = job.run(
79                'aapt dump badging %s' % self.apk_path,
80                ignore_status=True).stdout
81            match = re.compile(PKG_NAME_PATTERN).search(dump)
82            self._pkg_name = match.group('pkg_name') if match else ''
83        return self._pkg_name
84
85    def install(self, *extra_args):
86        """Installs the apk on the device.
87
88        Args:
89            extra_args: Additional flags to the ADB install command.
90                Note that '-r' is included by default.
91        """
92        self._ad.log.info('Installing app %s from %s' %
93                          (self.pkg_name, self.apk_path))
94        args = '-r %s' % ' '.join(extra_args)
95        self._ad.adb.install('%s %s' % (args, self.apk_path))
96
97    def uninstall(self, *extra_args):
98        """Uninstalls the apk from the device.
99
100        Args:
101            extra_args: Additional flags to the uninstall command.
102        """
103        self._ad.log.info('Uninstalling app %s' % self.pkg_name)
104        if not self.is_installed():
105            self._ad.log.warning('Unable to uninstall app %s. App is not '
106                                 'installed.' % self.pkg_name)
107            return
108        self._ad.adb.shell(
109            'pm uninstall %s %s' % (' '.join(extra_args), self.pkg_name))
110
111    def is_installed(self):
112        """Verifies that the apk is installed on the device.
113
114        Returns: True if the apk is installed on the device.
115        """
116        if not self.pkg_name:
117            self._ad.log.warning('No package name found for %s' % self.apk_path)
118            return False
119        return self._ad.is_apk_installed(self.pkg_name)
120