Uninstall apk if installed before re-installing PiperOrigin-RevId: 575106519
diff --git a/ui_automator/android_device.py b/ui_automator/android_device.py new file mode 100644 index 0000000..5d1f224 --- /dev/null +++ b/ui_automator/android_device.py
@@ -0,0 +1,48 @@ +"""Android device related methods which extend from UI Automator. + +Android device class is defined in `mobly.controller`. +""" + +from mobly import utils +from mobly.controllers import android_device + + +def is_apk_installed( + device: android_device.AndroidDevice, package_name: str +) -> bool: + """Checks if given package is already installed on the Android device. + + Args: + device: An AndroidDevice object. + package_name: The Android app package name. + + Raises: + adb.AdbError: When executing adb command fails. + + Returns: + True if package is installed. False otherwise. + """ + out = device.adb.shell(['pm', 'list', 'package']) + return bool(utils.grep('^package:%s$' % package_name, out)) + + +def install_apk(device: android_device.AndroidDevice, apk_path: str) -> None: + """Installs required apk snippet to the given Android device. + + Args: + device: An AndroidDevice object. + apk_path: The absolute file path where the apk is located. + """ + device.adb.install(['-r', '-g', apk_path]) + + +def uninstall_apk( + device: android_device.AndroidDevice, package_name: str +) -> None: + """Uninstalls required apk snippet on given Android device. + + Args: + device: An AndroidDevice object. + package_name: The Android app package name. + """ + device.adb.uninstall(package_name)
diff --git a/ui_automator/android_device_test.py b/ui_automator/android_device_test.py new file mode 100644 index 0000000..9f1fd46 --- /dev/null +++ b/ui_automator/android_device_test.py
@@ -0,0 +1,73 @@ +"""Unittest of android device extended methods.""" +import unittest +from unittest import mock + +from mobly.controllers import android_device +from mobly.controllers.android_device_lib import adb + +from ui_automator import android_device as ad + + +class UIAutomatorTest(unittest.TestCase): + + def setUp(self): + """This method will be run before each of the test methods in the class.""" + super().setUp() + self.mock_android_device = mock.patch.object( + android_device, 'AndroidDevice' + ).start() + + def test_is_apk_installed_returns_true_with_installed_apk(self): + self.mock_android_device.adb.shell.return_value = ( + b'package:installed.apk\npackage:a.apk\npackage:b.apk\n' + ) + + is_apk_installed = ad.is_apk_installed( + self.mock_android_device, 'installed.apk' + ) + + self.assertTrue(is_apk_installed) + + def test_is_apk_installed_returns_false_with_not_installed_apk(self): + self.mock_android_device.adb.shell.return_value = ( + b'package:installed.apk\npackage:a.apk\npackage:b.apk\n' + ) + + is_apk_installed = ad.is_apk_installed( + self.mock_android_device, 'notinstalled.apk' + ) + + self.assertFalse(is_apk_installed) + + def test_is_apk_installed_raises_an_error(self): + self.mock_android_device.adb.shell.side_effect = adb.AdbError( + cmd='adb.shell', + stderr=b'Run adb command failed.', + stdout=b'', + ret_code=1, + ) + + with self.assertRaisesRegex(adb.AdbError, r'Run adb command failed\.'): + ad.is_apk_installed(self.mock_android_device, 'fake.apk') + + def test_install_apk_installs_apk_with_correct_path(self): + apk_path = '/path/to/fake.apk' + + ad.install_apk(self.mock_android_device, apk_path) + + self.mock_android_device.adb.install.assert_called_once_with( + ['-r', '-g', apk_path] + ) + + def test_uninstall_apk_uninstalls_apk_with_given_package_name(self): + package_to_be_uninstalled = 'notinstalled.apk' + + ad.uninstall_apk(self.mock_android_device, package_to_be_uninstalled) + + self.mock_android_device.adb.uninstall.assert_called_once_with( + package_to_be_uninstalled + ) + + +if __name__ == '__main__': + unittest.main()
diff --git a/ui_automator/ui_automator.py b/ui_automator/ui_automator.py index fef9fea..63343c6 100644 --- a/ui_automator/ui_automator.py +++ b/ui_automator/ui_automator.py
@@ -26,10 +26,10 @@ from absl import app from absl import flags import inflection -from mobly import utils from mobly.controllers import android_device from mobly.controllers.android_device_lib import adb from mobly.snippet import errors as snippet_errors +from ui_automator import android_device as ad from ui_automator import errors @@ -184,8 +184,10 @@ 'No Android device connected to the host computer.' ) - if not self._is_apk_installed(self._connected_device, _MOBLY_SNIPPET_APK): - self._install_apk(self._connected_device, self._get_mbs_apk_path()) + if ad.is_apk_installed(self._connected_device, _MOBLY_SNIPPET_APK): + ad.uninstall_apk(self._connected_device, _MOBLY_SNIPPET_APK) + + ad.install_apk(self._connected_device, self._get_mbs_apk_path()) try: self._connected_device.load_snippet( @@ -255,35 +257,6 @@ f' device({self._connected_device.device_info["serial"]}).' ) from e - def _is_apk_installed( - self, device: android_device.AndroidDevice, package_name: str - ) -> bool: - """Checks if given package is already installed on the Android device. - - Args: - device: An AndroidDevice object. - package_name: The Android app package name. - - Raises: - adb.AdbError: When executing adb command fails. - - Returns: - True if package is installed. False otherwise. - """ - out = device.adb.shell(['pm', 'list', 'package']) - return bool(utils.grep('^package:%s$' % package_name, out)) - - def _install_apk( - self, device: android_device.AndroidDevice, apk_path: str - ) -> None: - """Installs required apk snippet to the given Android device. - - Args: - device: An AndroidDevice object. - apk_path: The absolute file path where the apk is located. - """ - device.adb.install(['-r', '-g', apk_path]) - def _get_mbs_apk_path(self) -> str: return os.path.join( os.path.dirname(os.path.abspath(__file__)),
diff --git a/ui_automator/ui_automator_test.py b/ui_automator/ui_automator_test.py index 74617a2..707264a 100644 --- a/ui_automator/ui_automator_test.py +++ b/ui_automator/ui_automator_test.py
@@ -184,11 +184,14 @@ self.mock_android_device.adb.install.assert_called_once_with( ['-r', '-g', '/path/to/android/app/snippet-0.0.0.apk'] ) + self.mock_android_device.adb.uninstall.assert_not_called() mock_dirname.assert_called_once() @mock.patch.object(android_device, 'get_all_instances', autospec=True) - @mock.patch.object(os.path, 'dirname', autospec=True) - def test_load_snippet_should_not_install_apk_when_apk_is_installed( + @mock.patch.object( + os.path, 'dirname', autospec=True, return_value='/path/to/' + ) + def test_load_snippet_uninstalls_apk_before_installing_it_when_installed( self, mock_dirname, mock_get_all_instances ): self.mock_android_device.adb.shell.return_value = ( @@ -199,8 +202,13 @@ self.ui_automator.load_snippet() - self.mock_android_device.adb.install.assert_not_called() - mock_dirname.assert_not_called() + self.mock_android_device.adb.uninstall.assert_called_with( + 'com.chip.interop.moblysnippet' + ) + self.mock_android_device.adb.install.assert_called_once_with( + ['-r', '-g', '/path/to/android/app/snippet-0.0.0.apk'] + ) + mock_dirname.assert_called_once() @mock.patch.object(ui_automator.UIAutomator, 'load_device') @mock.patch.object(ui_automator.UIAutomator, 'load_snippet')