blob: f5aa818517b6997201083e36115fca0d53aa6814 [file] [log] [blame]
Xianyuan Jia2e71ac72019-07-23 11:59:08 -07001#!/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 Jiae327f5b2019-09-19 11:15:04 -070017import os
Xianyuan Jia2e71ac72019-07-23 11:59:08 -070018import re
19
Xianyuan Jia9c00d5c2020-10-29 14:59:58 -070020from acts.error import ActsError
Xianyuan Jia2e71ac72019-07-23 11:59:08 -070021from acts.libs.proc import job
22
23PKG_NAME_PATTERN = r"^package:\s+name='(?P<pkg_name>.*?)'"
Xianyuan Jiae327f5b2019-09-19 11:15:04 -070024PM_PATH_PATTERN = r"^package:(?P<apk_path>.*)"
Xianyuan Jia2e71ac72019-07-23 11:59:08 -070025
26
Xianyuan Jia9c00d5c2020-10-29 14:59:58 -070027class AppInstallerError(ActsError):
Hector5f09bf02020-05-01 19:13:47 -070028 """Exception class for AppInstaller's errors."""
Hector5f09bf02020-05-01 19:13:47 -070029
30
Xianyuan Jia2e71ac72019-07-23 11:59:08 -070031class AppInstaller(object):
Xianyuan Jia90905e52020-01-02 15:32:10 -080032 """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 Jia2e71ac72019-07-23 11:59:08 -070037
38 Args:
Xianyuan Jia90905e52020-01-02 15:32:10 -080039 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 """
Hector5f09bf02020-05-01 19:13:47 -070082 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 Jia90905e52020-01-02 15:32:10 -080089 if self._pkg_name is None:
90 dump = job.run(
Hector5f09bf02020-05-01 19:13:47 -070091 'aapt dump badging %s' % self.apk_path).stdout
Xianyuan Jia90905e52020-01-02 15:32:10 -080092 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 Jia2e71ac72019-07-23 11:59:08 -0700100 extra_args: Additional flags to the ADB install command.
101 Note that '-r' is included by default.
102 """
Xianyuan Jia1e4d4f02020-01-16 12:13:19 -0800103 self._ad.log.info('Installing app %s from %s' %
104 (self.pkg_name, self.apk_path))
Xianyuan Jia2e71ac72019-07-23 11:59:08 -0700105 args = '-r %s' % ' '.join(extra_args)
Xianyuan Jia90905e52020-01-02 15:32:10 -0800106 self._ad.adb.install('%s %s' % (args, self.apk_path))
Xianyuan Jia2e71ac72019-07-23 11:59:08 -0700107
Xianyuan Jia90905e52020-01-02 15:32:10 -0800108 def uninstall(self, *extra_args):
109 """Uninstalls the apk from the device.
Xianyuan Jia2e71ac72019-07-23 11:59:08 -0700110
111 Args:
Xianyuan Jia2e71ac72019-07-23 11:59:08 -0700112 extra_args: Additional flags to the uninstall command.
113 """
Xianyuan Jia90905e52020-01-02 15:32:10 -0800114 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 Jia2e71ac72019-07-23 11:59:08 -0700121
Xianyuan Jia90905e52020-01-02 15:32:10 -0800122 def is_installed(self):
123 """Verifies that the apk is installed on the device.
Xianyuan Jia2e71ac72019-07-23 11:59:08 -0700124
125 Returns: True if the apk is installed on the device.
126 """
Xianyuan Jia90905e52020-01-02 15:32:10 -0800127 if not self.pkg_name:
128 self._ad.log.warning('No package name found for %s' % self.apk_path)
Xianyuan Jia2e71ac72019-07-23 11:59:08 -0700129 return False
Xianyuan Jia90905e52020-01-02 15:32:10 -0800130 return self._ad.is_apk_installed(self.pkg_name)