Merge "Redesign AppInstaller to better keep track of individual apks"
diff --git a/acts/framework/acts/test_utils/instrumentation/app_installer.py b/acts/framework/acts/test_utils/instrumentation/app_installer.py
index c27d7e7..5c74f8c 100644
--- a/acts/framework/acts/test_utils/instrumentation/app_installer.py
+++ b/acts/framework/acts/test_utils/instrumentation/app_installer.py
@@ -24,81 +24,96 @@
class AppInstaller(object):
- """Class for installing apps on an Android device."""
- def __init__(self, device):
- self.ad = device
- self._pkgs = {}
-
- def install(self, apk_path, *extra_args):
- """Installs an apk on the device.
+ """Class that represents an app on an Android device. Includes methods
+ for install, uninstall, and getting info.
+ """
+ def __init__(self, ad, apk_path):
+ """Initializes an AppInstaller.
Args:
- apk_path: Path to the apk to install
+ ad: device to install the apk
+ apk_path: path to the apk
+ """
+ self._ad = ad
+ self._apk_path = apk_path
+ self._pkg_name = None
+
+ @staticmethod
+ def pull_from_device(ad, pkg_name, dest):
+ """Initializes an AppInstaller by pulling the apk file from the device,
+ given the package name
+
+ Args:
+ ad: device on which the apk is installed
+ pkg_name: package name
+ dest: destination directory
+ (Note: If path represents a directory, it must already exist as
+ a directory)
+
+ Returns: AppInstaller object representing the pulled apk, or None if
+ package not installed
+ """
+ if not ad.is_apk_installed(pkg_name):
+ ad.log.warning('Unable to find package %s on device. Pull aborted.'
+ % pkg_name)
+ return None
+ path_on_device = re.compile(PM_PATH_PATTERN).search(
+ ad.adb.shell('pm path %s' % pkg_name)).group('apk_path')
+ ad.pull_files(path_on_device, dest)
+ if os.path.isdir(dest):
+ dest = os.path.join(dest, os.path.basename(path_on_device))
+ return AppInstaller(ad, dest)
+
+ @property
+ def apk_path(self):
+ return self._apk_path
+
+ @property
+ def pkg_name(self):
+ """Get the package name corresponding to the apk from aapt
+
+ Returns: The package name, or empty string if not found.
+ """
+ if self._pkg_name is None:
+ dump = job.run(
+ 'aapt dump badging %s' % self.apk_path,
+ ignore_status=True).stdout
+ match = re.compile(PKG_NAME_PATTERN).search(dump)
+ self._pkg_name = match.group('pkg_name') if match else ''
+ return self._pkg_name
+
+ def install(self, *extra_args):
+ """Installs the apk on the device.
+
+ Args:
extra_args: Additional flags to the ADB install command.
Note that '-r' is included by default.
"""
- self.ad.log.info('Installing app %s' % apk_path)
- self.ad.ensure_screen_on()
+ self._ad.log.info('Installing app %s' % self.apk_path)
+ self._ad.ensure_screen_on()
args = '-r %s' % ' '.join(extra_args)
- self.ad.adb.install('%s %s' % (args, apk_path))
+ self._ad.adb.install('%s %s' % (args, self.apk_path))
- def uninstall(self, apk_path, *extra_args):
- """Finds the package corresponding to the apk and uninstalls it from the
- device.
+ def uninstall(self, *extra_args):
+ """Uninstalls the apk from the device.
Args:
- apk_path: Path to the apk
extra_args: Additional flags to the uninstall command.
"""
- if self.is_installed(apk_path):
- pkg_name = self.get_package_name(apk_path)
- self.ad.log.info('Uninstalling app %s' % pkg_name)
- self.ad.adb.shell(
- 'pm uninstall %s %s' % (' '.join(extra_args), pkg_name))
+ self._ad.log.info('Uninstalling app %s' % self.pkg_name)
+ if not self.is_installed():
+ self._ad.log.warning('Unable to uninstall app %s. App is not '
+ 'installed.' % self.pkg_name)
+ return
+ self._ad.adb.shell(
+ 'pm uninstall %s %s' % (' '.join(extra_args), self.pkg_name))
- def is_installed(self, apk_path):
- """Verifies that an apk is installed on the device.
-
- Args:
- apk_path: Path to the apk
+ def is_installed(self):
+ """Verifies that the apk is installed on the device.
Returns: True if the apk is installed on the device.
"""
- pkg_name = self.get_package_name(apk_path)
- if not pkg_name:
- self.ad.log.warning('No package name found for %s' % apk_path)
+ if not self.pkg_name:
+ self._ad.log.warning('No package name found for %s' % self.apk_path)
return False
- return self.ad.is_apk_installed(pkg_name)
-
- def get_package_name(self, apk_path):
- """Get the package name corresponding to the apk from aapt
-
- Args:
- apk_path: Path to the apk
-
- Returns: The package name
- """
- if apk_path not in self._pkgs:
- dump = job.run(
- 'aapt dump badging %s' % apk_path, ignore_status=True).stdout
- match = re.compile(PKG_NAME_PATTERN).search(dump)
- self._pkgs[apk_path] = match.group('pkg_name') if match else ''
- return self._pkgs[apk_path]
-
- def pull_apk(self, package_name, dest):
- """Pull the corresponding apk file from device given the package name
-
- Args:
- package_name: Package name
- dest: Destination directory
-
- Returns: Path to the pulled apk, or None if package not installed
- """
- if not self.ad.is_apk_installed(package_name):
- self.ad.log.warning('Unable to find package %s on device. Pull '
- 'aborted.' % package_name)
- return None
- apk_path = re.compile(PM_PATH_PATTERN).search(
- self.ad.adb.shell('pm path %s' % package_name)).group('apk_path')
- self.ad.pull_files(apk_path, dest)
- return os.path.join(dest, os.path.basename(apk_path))
+ return self._ad.is_apk_installed(self.pkg_name)
diff --git a/acts/framework/acts/test_utils/instrumentation/instrumentation_base_test.py b/acts/framework/acts/test_utils/instrumentation/instrumentation_base_test.py
index dbf8360..6b85a87 100644
--- a/acts/framework/acts/test_utils/instrumentation/instrumentation_base_test.py
+++ b/acts/framework/acts/test_utils/instrumentation/instrumentation_base_test.py
@@ -22,10 +22,10 @@
from acts import context
from acts import utils
from acts.keys import Config
-from acts.test_utils.instrumentation import app_installer
from acts.test_utils.instrumentation import instrumentation_proto_parser \
as proto_parser
from acts.test_utils.instrumentation.adb_commands import common
+from acts.test_utils.instrumentation.app_installer import AppInstaller
from acts.test_utils.instrumentation.config_wrapper import ConfigWrapper
from acts.test_utils.instrumentation.instrumentation_command_builder import \
InstrumentationCommandBuilder
@@ -126,7 +126,6 @@
def setup_class(self):
"""Class setup"""
self.ad_dut = self.android_devices[0]
- self.ad_apps = app_installer.AppInstaller(self.ad_dut)
self._prepare_device()
def teardown_class(self):
@@ -274,15 +273,15 @@
# Install PermissionUtils.apk
permissions_apk_path = self._instrumentation_config.get_file(
'permissions_apk')
- self.ad_apps.install(permissions_apk_path)
- if not self.ad_apps.is_installed(permissions_apk_path):
+ permission_utils = AppInstaller(self.ad_dut, permissions_apk_path)
+ permission_utils.install()
+ if not permission_utils.is_installed():
raise InstrumentationTestError(
'Failed to install PermissionUtils.apk, abort!')
- package_name = self.ad_apps.get_package_name(permissions_apk_path)
# Run the instrumentation command
cmd_builder = InstrumentationCommandBuilder()
- cmd_builder.set_manifest_package(package_name)
+ cmd_builder.set_manifest_package(permission_utils.pkg_name)
cmd_builder.set_runner('.PermissionInstrumentation')
cmd_builder.add_flag('-w')
cmd_builder.add_flag('-r')
@@ -292,4 +291,4 @@
self.adb_run(cmd)
# Uninstall PermissionUtils.apk
- self.ad_apps.uninstall(permissions_apk_path)
+ permission_utils.uninstall()