Xianyuan Jia | 2e71ac7 | 2019-07-23 11:59:08 -0700 | [diff] [blame] | 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 | |
Xianyuan Jia | e327f5b | 2019-09-19 11:15:04 -0700 | [diff] [blame] | 17 | import os |
Xianyuan Jia | 2e71ac7 | 2019-07-23 11:59:08 -0700 | [diff] [blame] | 18 | import re |
| 19 | |
Xianyuan Jia | 9c00d5c | 2020-10-29 14:59:58 -0700 | [diff] [blame] | 20 | from acts.error import ActsError |
Xianyuan Jia | 2e71ac7 | 2019-07-23 11:59:08 -0700 | [diff] [blame] | 21 | from acts.libs.proc import job |
| 22 | |
| 23 | PKG_NAME_PATTERN = r"^package:\s+name='(?P<pkg_name>.*?)'" |
Xianyuan Jia | e327f5b | 2019-09-19 11:15:04 -0700 | [diff] [blame] | 24 | PM_PATH_PATTERN = r"^package:(?P<apk_path>.*)" |
Xianyuan Jia | 2e71ac7 | 2019-07-23 11:59:08 -0700 | [diff] [blame] | 25 | |
| 26 | |
Xianyuan Jia | 9c00d5c | 2020-10-29 14:59:58 -0700 | [diff] [blame] | 27 | class AppInstallerError(ActsError): |
Hector | 5f09bf0 | 2020-05-01 19:13:47 -0700 | [diff] [blame] | 28 | """Exception class for AppInstaller's errors.""" |
Hector | 5f09bf0 | 2020-05-01 19:13:47 -0700 | [diff] [blame] | 29 | |
| 30 | |
Xianyuan Jia | 2e71ac7 | 2019-07-23 11:59:08 -0700 | [diff] [blame] | 31 | class AppInstaller(object): |
Xianyuan Jia | 90905e5 | 2020-01-02 15:32:10 -0800 | [diff] [blame] | 32 | """Class that represents an app on an Android device. Includes methods |
| 33 | for install, uninstall, and getting info. |
| 34 | """ |
| 35 | def __init__(self, ad, apk_path): |
| 36 | """Initializes an AppInstaller. |
Xianyuan Jia | 2e71ac7 | 2019-07-23 11:59:08 -0700 | [diff] [blame] | 37 | |
| 38 | Args: |
Xianyuan Jia | 90905e5 | 2020-01-02 15:32:10 -0800 | [diff] [blame] | 39 | ad: device to install the apk |
| 40 | apk_path: path to the apk |
| 41 | """ |
| 42 | self._ad = ad |
| 43 | self._apk_path = apk_path |
| 44 | self._pkg_name = None |
| 45 | |
| 46 | @staticmethod |
| 47 | def pull_from_device(ad, pkg_name, dest): |
| 48 | """Initializes an AppInstaller by pulling the apk file from the device, |
| 49 | given the package name |
| 50 | |
| 51 | Args: |
| 52 | ad: device on which the apk is installed |
| 53 | pkg_name: package name |
| 54 | dest: destination directory |
| 55 | (Note: If path represents a directory, it must already exist as |
| 56 | a directory) |
| 57 | |
| 58 | Returns: AppInstaller object representing the pulled apk, or None if |
| 59 | package not installed |
| 60 | """ |
| 61 | if not ad.is_apk_installed(pkg_name): |
| 62 | ad.log.warning('Unable to find package %s on device. Pull aborted.' |
| 63 | % pkg_name) |
| 64 | return None |
| 65 | path_on_device = re.compile(PM_PATH_PATTERN).search( |
| 66 | ad.adb.shell('pm path %s' % pkg_name)).group('apk_path') |
| 67 | ad.pull_files(path_on_device, dest) |
| 68 | if os.path.isdir(dest): |
| 69 | dest = os.path.join(dest, os.path.basename(path_on_device)) |
| 70 | return AppInstaller(ad, dest) |
| 71 | |
| 72 | @property |
| 73 | def apk_path(self): |
| 74 | return self._apk_path |
| 75 | |
| 76 | @property |
| 77 | def pkg_name(self): |
| 78 | """Get the package name corresponding to the apk from aapt |
| 79 | |
| 80 | Returns: The package name, or empty string if not found. |
| 81 | """ |
Hector | 5f09bf0 | 2020-05-01 19:13:47 -0700 | [diff] [blame] | 82 | try: |
| 83 | job.run('which aapt') |
| 84 | except job.Error: |
| 85 | raise AppInstallerError('aapt not found or is not executable. Make ' |
| 86 | 'sure aapt is reachable from PATH and' |
| 87 | 'executable') |
| 88 | |
Xianyuan Jia | 90905e5 | 2020-01-02 15:32:10 -0800 | [diff] [blame] | 89 | if self._pkg_name is None: |
| 90 | dump = job.run( |
Hector | 5f09bf0 | 2020-05-01 19:13:47 -0700 | [diff] [blame] | 91 | 'aapt dump badging %s' % self.apk_path).stdout |
Xianyuan Jia | 90905e5 | 2020-01-02 15:32:10 -0800 | [diff] [blame] | 92 | match = re.compile(PKG_NAME_PATTERN).search(dump) |
| 93 | self._pkg_name = match.group('pkg_name') if match else '' |
| 94 | return self._pkg_name |
| 95 | |
| 96 | def install(self, *extra_args): |
| 97 | """Installs the apk on the device. |
| 98 | |
| 99 | Args: |
Xianyuan Jia | 2e71ac7 | 2019-07-23 11:59:08 -0700 | [diff] [blame] | 100 | extra_args: Additional flags to the ADB install command. |
| 101 | Note that '-r' is included by default. |
| 102 | """ |
Xianyuan Jia | 1e4d4f0 | 2020-01-16 12:13:19 -0800 | [diff] [blame] | 103 | self._ad.log.info('Installing app %s from %s' % |
| 104 | (self.pkg_name, self.apk_path)) |
Xianyuan Jia | 2e71ac7 | 2019-07-23 11:59:08 -0700 | [diff] [blame] | 105 | args = '-r %s' % ' '.join(extra_args) |
Xianyuan Jia | 90905e5 | 2020-01-02 15:32:10 -0800 | [diff] [blame] | 106 | self._ad.adb.install('%s %s' % (args, self.apk_path)) |
Xianyuan Jia | 2e71ac7 | 2019-07-23 11:59:08 -0700 | [diff] [blame] | 107 | |
Xianyuan Jia | 90905e5 | 2020-01-02 15:32:10 -0800 | [diff] [blame] | 108 | def uninstall(self, *extra_args): |
| 109 | """Uninstalls the apk from the device. |
Xianyuan Jia | 2e71ac7 | 2019-07-23 11:59:08 -0700 | [diff] [blame] | 110 | |
| 111 | Args: |
Xianyuan Jia | 2e71ac7 | 2019-07-23 11:59:08 -0700 | [diff] [blame] | 112 | extra_args: Additional flags to the uninstall command. |
| 113 | """ |
Xianyuan Jia | 90905e5 | 2020-01-02 15:32:10 -0800 | [diff] [blame] | 114 | self._ad.log.info('Uninstalling app %s' % self.pkg_name) |
| 115 | if not self.is_installed(): |
| 116 | self._ad.log.warning('Unable to uninstall app %s. App is not ' |
| 117 | 'installed.' % self.pkg_name) |
| 118 | return |
| 119 | self._ad.adb.shell( |
| 120 | 'pm uninstall %s %s' % (' '.join(extra_args), self.pkg_name)) |
Xianyuan Jia | 2e71ac7 | 2019-07-23 11:59:08 -0700 | [diff] [blame] | 121 | |
Xianyuan Jia | 90905e5 | 2020-01-02 15:32:10 -0800 | [diff] [blame] | 122 | def is_installed(self): |
| 123 | """Verifies that the apk is installed on the device. |
Xianyuan Jia | 2e71ac7 | 2019-07-23 11:59:08 -0700 | [diff] [blame] | 124 | |
| 125 | Returns: True if the apk is installed on the device. |
| 126 | """ |
Xianyuan Jia | 90905e5 | 2020-01-02 15:32:10 -0800 | [diff] [blame] | 127 | if not self.pkg_name: |
| 128 | self._ad.log.warning('No package name found for %s' % self.apk_path) |
Xianyuan Jia | 2e71ac7 | 2019-07-23 11:59:08 -0700 | [diff] [blame] | 129 | return False |
Xianyuan Jia | 90905e5 | 2020-01-02 15:32:10 -0800 | [diff] [blame] | 130 | return self._ad.is_apk_installed(self.pkg_name) |