Check apk version to decide whether to uninstall it

PiperOrigin-RevId: 579057715
diff --git a/ui_automator/android_device.py b/ui_automator/android_device.py
index 5d1f224..c517377 100644
--- a/ui_automator/android_device.py
+++ b/ui_automator/android_device.py
@@ -46,3 +46,28 @@
     package_name: The Android app package name.
   """
   device.adb.uninstall(package_name)
+
+
+def is_apk_version_correct(
+    device: android_device.AndroidDevice, package_name: str, version: str
+) -> bool:
+  """Checks if the version of package is given version on the Android device.
+
+  Args:
+    device: An AndroidDevice object.
+    package_name: The Android app package name.
+    version: The specific version of app package.
+
+  Returns:
+    True if the version of package matches given version. False otherwise.
+
+  Raises:
+    adb.AdbError: When executing adb command fails.
+  """
+  grep_version_name = utils.grep(
+      'versionName', device.adb.shell(['dumpsys', 'package', package_name])
+  )
+
+  return (
+      bool(grep_version_name) and grep_version_name[0].split('=')[1] == version
+  )
diff --git a/ui_automator/android_device_test.py b/ui_automator/android_device_test.py
index 9f1fd46..595f680 100644
--- a/ui_automator/android_device_test.py
+++ b/ui_automator/android_device_test.py
@@ -1,4 +1,5 @@
 """Unittest of android device extended methods."""
+
 import unittest
 from unittest import mock
 
@@ -68,6 +69,35 @@
         package_to_be_uninstalled
     )
 
+  def test_is_apk_version_correct_returns_true(self):
+    self.mock_android_device.adb.shell.return_value = b'versionName=1.2.3\n'
+
+    self.assertTrue(
+        ad.is_apk_version_correct(
+            self.mock_android_device, 'installed.apk', '1.2.3'
+        )
+    )
+
+  def test_is_apk_version_correct_returns_false_with_not_installed_apk(self):
+    self.mock_android_device.adb.shell.return_value = (
+        b'Unable to find package\n'
+    )
+
+    self.assertFalse(
+        ad.is_apk_version_correct(
+            self.mock_android_device, 'notinstalled.apk', '1.2.3'
+        )
+    )
+
+  def test_is_apk_version_correct_returns_false_with_incorrect_version(self):
+    self.mock_android_device.adb.shell.return_value = b'versionName=1.2.4\n'
+
+    self.assertFalse(
+        ad.is_apk_version_correct(
+            self.mock_android_device, 'installed.apk', '1.2.3'
+        )
+    )
+
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/ui_automator/ui_automator.py b/ui_automator/ui_automator.py
index bdea2d1..a9edc5b 100644
--- a/ui_automator/ui_automator.py
+++ b/ui_automator/ui_automator.py
@@ -29,9 +29,11 @@
 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 android_device as ad
 from ui_automator import errors
+from ui_automator import version
+
 
 _GOOGLE_HOME_APP = {
     'id': 'gha',
@@ -184,10 +186,17 @@
           'No Android device connected to the host computer.'
       )
 
-    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())
+    is_apk_installed = ad.is_apk_installed(
+        self._connected_device, _MOBLY_SNIPPET_APK
+    )
+    if not is_apk_installed or not ad.is_apk_version_correct(
+        self._connected_device,
+        _MOBLY_SNIPPET_APK,
+        version.VERSION,
+    ):
+      if is_apk_installed:
+        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(
diff --git a/ui_automator/ui_automator_test.py b/ui_automator/ui_automator_test.py
index 8063411..4de7691 100644
--- a/ui_automator/ui_automator_test.py
+++ b/ui_automator/ui_automator_test.py
@@ -25,6 +25,7 @@
 
 from ui_automator import errors
 from ui_automator import ui_automator
+from ui_automator import version
 
 _FAKE_MATTER_DEVICE_NAME = 'fake-matter-device-name'
 _FAKE_GHA_ROOM = 'Office'
@@ -191,12 +192,33 @@
   @mock.patch.object(
       os.path, 'dirname', autospec=True, return_value='/path/to/'
   )
-  def test_load_snippet_uninstalls_apk_before_installing_it_when_installed(
+  def test_load_snippet_should_not_install_apk_when_correct_apk_installed(
       self, mock_dirname, mock_get_all_instances
   ):
-    self.mock_android_device.adb.shell.return_value = (
-        b'package:com.chip.interop.moblysnippet'
-    )
+    self.mock_android_device.adb.shell.side_effect = [
+        b'package:com.chip.interop.moblysnippet\n',
+        f'versionName={version.VERSION}'.encode('utf-8'),
+    ]
+    mock_get_all_instances.return_value = [self.mock_android_device]
+    self.ui_automator.load_device()
+
+    self.ui_automator.load_snippet()
+
+    self.mock_android_device.adb.install.assert_not_called()
+    self.mock_android_device.adb.uninstall.assert_not_called()
+    mock_dirname.assert_not_called()
+
+  @mock.patch.object(android_device, 'get_all_instances', autospec=True)
+  @mock.patch.object(
+      os.path, 'dirname', autospec=True, return_value='/path/to/'
+  )
+  def test_load_snippet_uninstalls_apk_before_installing_it_with_incorrect_apk(
+      self, mock_dirname, mock_get_all_instances
+  ):
+    self.mock_android_device.adb.shell.side_effect = [
+        b'package:com.chip.interop.moblysnippet\n',
+        b'versionName=fake.version',
+    ]
     mock_get_all_instances.return_value = [self.mock_android_device]
     self.ui_automator.load_device()
 
@@ -210,6 +232,28 @@
     )
     mock_dirname.assert_called_once()
 
+  @mock.patch.object(android_device, 'get_all_instances', autospec=True)
+  @mock.patch.object(
+      os.path, 'dirname', autospec=True, return_value='/path/to/'
+  )
+  def test_load_snippet_installs_apk_when_no_apk_installed(
+      self, mock_dirname, mock_get_all_instances
+  ):
+    self.mock_android_device.adb.shell.side_effect = [
+        b'package:installed.apk\n',
+        b'Unable to find package\n',
+    ]
+    mock_get_all_instances.return_value = [self.mock_android_device]
+    self.ui_automator.load_device()
+
+    self.ui_automator.load_snippet()
+
+    self.mock_android_device.adb.uninstall.assert_not_called()
+    self.mock_android_device.adb.install.assert_called_once_with(
+        ['-r', '-g', '/path/to/android/app/snippet-0.1.0.apk']
+    )
+    mock_dirname.assert_called_once()
+
   @mock.patch.object(ui_automator.UIAutomator, 'load_device')
   @mock.patch.object(ui_automator.UIAutomator, 'load_snippet')
   def test_get_android_device_ready_raises_an_error_when_load_device_throws_an_error(