[Local Agent] Add ui automator to interact with snippets

Functional proof: https://paste.googleplex.com/6453086699651072
Test coverage: https://paste.googleplex.com/4999773617651712
Design doc: go/ghts:dd:ui-auto

Bug: b/282592569
Change-Id: Ifd93ea70bbd2b5be72c4d9f39deb93d2f26e6ec5
diff --git a/local_agent/local_agent.py b/local_agent/local_agent.py
index 5400fa1..afae5a8 100644
--- a/local_agent/local_agent.py
+++ b/local_agent/local_agent.py
@@ -329,7 +329,6 @@
                 resp = self._translator.dispatch_to_cmd_handler(rpc_request)
 
             elif req_type == RpcRequestType.COMMISSION_TO_GOOGLE_FABRIC:
-                # TODO(b/282592569): Move commission_to_google_fabric out of translator.
                 resp = self._translator.commission_to_google_fabric(rpc_request)
 
             else:
diff --git a/local_agent/tests/unit_tests/test_local_agent.py b/local_agent/tests/unit_tests/test_local_agent.py
index 6d05c94..f318c2c 100644
--- a/local_agent/tests/unit_tests/test_local_agent.py
+++ b/local_agent/tests/unit_tests/test_local_agent.py
@@ -41,6 +41,9 @@
 _FAKE_ERROR_MSG = 'fake-error-msg'
 _FAKE_COMMISSION_ERROR_MSG = 'Unable to commission the device.'
 _FAKE_ARTIFACTS_DIR = 'fake-artifacts-dir'
+_FAKE_PAIRING_CODE = '34970112332'
+_FAKE_MATTER_DEVICE_NAME = 'fake-matter-device-name'
+_FAKE_GHA_ROOM = 'Office'
 _START_TEST_SUITE = 'startTestSuite'
 _END_TEST_SUITE = 'endTestSuite'
 _COMMISSION_TO_GOOGLE_FABRIC = 'commissionToGoogleFabric'
@@ -619,7 +622,12 @@
     def test_handle_rpc_request_commission_to_google_fabric_on_success(self, mock_commission):
         """Verifies handle_rpc_request to commission a device to google fabric on success."""
         mock_commission.return_value = _FAKE_RPC_RESPONSE
-        fake_rpc_request = {'method': _COMMISSION_TO_GOOGLE_FABRIC}
+        fake_rpc_request = {
+            'method': _COMMISSION_TO_GOOGLE_FABRIC,
+            'pairingCode': _FAKE_PAIRING_CODE,
+            'deviceName': _FAKE_MATTER_DEVICE_NAME,
+            'ghaRoom': _FAKE_GHA_ROOM,
+        }
 
         rpc_response = self.proc._handle_rpc_request(fake_rpc_request)
 
diff --git a/local_agent/tests/unit_tests/test_translation_layer.py b/local_agent/tests/unit_tests/test_translation_layer.py
index f292b9c..ce3d204 100644
--- a/local_agent/tests/unit_tests/test_translation_layer.py
+++ b/local_agent/tests/unit_tests/test_translation_layer.py
@@ -17,10 +17,12 @@
 import unittest
 from unittest import mock
 
-from local_agent import ams_client
 from local_agent import errors as agent_errors
 from local_agent.translation_layer import gdm_manager
 from local_agent.translation_layer import translation_layer
+from mobly.controllers import android_device
+from ui_automator import errors as ua_errors
+from ui_automator import ui_automator
 
 
 ####################### Fake data for unit test #############################
@@ -28,7 +30,10 @@
 _FAKE_DEVICE_TYPE = 'fake-device-type'
 _FAKE_DEVICE_ID = 'fake-device-id'
 _FAKE_CAPABILITY = 'fake-device-capability'
-_FAKE_PAIRING_CODE = 'fake-pairing-code'
+_FAKE_PAIRING_CODE = '34970112332'
+_FAKE_MATTER_DEVICE_NAME = 'fake-matter-device-name'
+_FAKE_GHA_ROOM = 'Office'
+_FAKE_SERIAL = 'fake-serial'
 _SET_ON = 'setOn'
 _SET_LOCK = 'setLock'
 _COMMISSION_TO_GOOGLE_FABRIC = 'commissionToGoogleFabric'
@@ -220,21 +225,140 @@
         self.mock_client.send_rpc_response.assert_called_once()
         self.assertTrue(self.translator.is_rpc_timeout(_FAKE_DEVICE_ID))
 
-    def test_commission_to_google_fabric_on_failure(self):
-        """Verifies commission_to_google_fabric throws an error when pairing code not found."""
+    def test_commission_to_google_fabric_raises_an_error_without_pairing_code(
+      self,
+    ):
+        """Verifies commission_to_google_fabric method raises an error when no pairingCode was provided."""
         invalid_rpc_request = rpc_request(_COMMISSION_TO_GOOGLE_FABRIC, {})
         error_msg = 'Invalid rpc command, no pairingCode in params was found.'
+
         with self.assertRaisesRegex(agent_errors.InvalidRPCError, error_msg):
             self.translator.commission_to_google_fabric(invalid_rpc_request)
 
-    def test_commission_to_google_fabric_on_success(self):
-        """Verifies commission_to_google_fabric on success when pairing code is provided."""
-        valid_rpc_request = rpc_request(
-            _COMMISSION_TO_GOOGLE_FABRIC, {'pairingCode': _FAKE_PAIRING_CODE})
-        response = self.translator.commission_to_google_fabric(valid_rpc_request)
+    def test_commission_to_google_fabric_raises_an_error_without_device_name(
+        self,
+    ):
+        """Verifies commission_to_google_fabric method raises an error when no deviceName was provided."""
+        invalid_rpc_request = rpc_request(
+            _COMMISSION_TO_GOOGLE_FABRIC,
+            {
+                'pairingCode': _FAKE_PAIRING_CODE,
+            },
+        )
+        error_msg = 'Invalid rpc command, no deviceName in params was found.'
 
-        # Id is fake and it was made by `rpc_request` wrapper.
-        self.assertEqual(response, {'id': 0, 'jsonrpc': '2.0', 'result': {}})
+        with self.assertRaisesRegex(agent_errors.InvalidRPCError, error_msg):
+            self.translator.commission_to_google_fabric(invalid_rpc_request)
+
+    def test_commission_to_google_fabric_raises_an_error_without_gha_room(self):
+        """Verifies commission_to_google_fabric method raises an error when no ghaRoom was provided."""
+        invalid_rpc_request = rpc_request(
+            _COMMISSION_TO_GOOGLE_FABRIC,
+            {
+                'pairingCode': _FAKE_PAIRING_CODE,
+                'deviceName': _FAKE_MATTER_DEVICE_NAME,
+            },
+        )
+        error_msg = 'Invalid rpc command, no ghaRoom in params was found.'
+
+        with self.assertRaisesRegex(agent_errors.InvalidRPCError, error_msg):
+            self.translator.commission_to_google_fabric(invalid_rpc_request)
+
+    @mock.patch.object(android_device, 'AndroidDevice')
+    @mock.patch.object(android_device, 'get_all_instances')
+    @mock.patch.object(translation_layer, 'logger')
+    @mock.patch.object(ui_automator.UIAutomator, 'commission_device')
+    def test_commission_to_google_fabric_raises_an_error_when_commissioning_fails(
+        self,
+        mock_commission_device,
+        mock_logger,
+        mock_get_all_instances,
+        mock_android_device,
+    ):
+        """Verifies commission_to_google_fabric method raises an error when running commissioning method in snippet apk fails."""
+        mock_get_all_instances.return_value = [
+            android_device.AndroidDevice(_FAKE_SERIAL)
+        ]
+        expected_error_message = (
+            'Unable to continue automated commissioning process on'
+            ' device(fake-serial).'
+        )
+        mock_commission_device.side_effect = ua_errors.MoblySnippetError(
+            expected_error_message
+        )
+
+        response = self.translator.commission_to_google_fabric(
+            rpc_request(
+                _COMMISSION_TO_GOOGLE_FABRIC,
+                {
+                    'pairingCode': _FAKE_PAIRING_CODE,
+                    'deviceName': _FAKE_MATTER_DEVICE_NAME,
+                    'ghaRoom': _FAKE_GHA_ROOM,
+                },
+            )
+        )
+
+        self.assertEqual(
+            response,
+            {
+                'jsonrpc': '2.0',
+                'id': 0,
+                'result': {
+                    'isCommissioned': False,
+                    'errorLog': expected_error_message,
+                },
+            },
+        )
+        mock_commission_device.assert_called_once_with(
+            pairing_code=_FAKE_PAIRING_CODE,
+            device_name=_FAKE_MATTER_DEVICE_NAME,
+            gha_room=_FAKE_GHA_ROOM,
+        )
+        mock_logger.info.assert_called_once_with(
+            f'Completed request for 0, pairingCode: {_FAKE_PAIRING_CODE},'
+            f' deviceName: {_FAKE_MATTER_DEVICE_NAME}, ghaRoom: {_FAKE_GHA_ROOM}'
+        )
+
+    @mock.patch.object(android_device, 'AndroidDevice')
+    @mock.patch.object(android_device, 'get_all_instances')
+    @mock.patch.object(translation_layer, 'logger')
+    @mock.patch.object(ui_automator.UIAutomator, 'commission_device')
+    def test_commission_to_google_fabric_on_success(
+        self,
+        mock_commission_device,
+        mock_logger,
+        mock_get_all_instances,
+        mock_android_device,
+    ):
+        """Verifies commission_to_google_fabric on success when required params are provided."""
+        mock_get_all_instances.return_value = [
+            android_device.AndroidDevice(_FAKE_SERIAL)
+        ]
+
+        response = self.translator.commission_to_google_fabric(
+            rpc_request(
+                _COMMISSION_TO_GOOGLE_FABRIC,
+                {
+                    'pairingCode': _FAKE_PAIRING_CODE,
+                    'deviceName': _FAKE_MATTER_DEVICE_NAME,
+                    'ghaRoom': _FAKE_GHA_ROOM,
+                },
+            )
+        )
+
+        mock_commission_device.assert_called_once_with(
+            pairing_code=_FAKE_PAIRING_CODE,
+            device_name=_FAKE_MATTER_DEVICE_NAME,
+            gha_room=_FAKE_GHA_ROOM,
+        )
+        mock_logger.info.assert_called_once_with(
+            f'Completed request for 0, pairingCode: {_FAKE_PAIRING_CODE},'
+            f' deviceName: {_FAKE_MATTER_DEVICE_NAME}, ghaRoom: {_FAKE_GHA_ROOM}'
+        )
+        self.assertEqual(
+            response,
+            {'jsonrpc': '2.0', 'id': 0, 'result': {'isCommissioned': True}},
+        )
 
 if __name__ == '__main__':
     unittest.main(failfast=True)
diff --git a/local_agent/translation_layer/translation_layer.py b/local_agent/translation_layer/translation_layer.py
index 5da05fe..b652926 100644
--- a/local_agent/translation_layer/translation_layer.py
+++ b/local_agent/translation_layer/translation_layer.py
@@ -25,7 +25,8 @@
 from local_agent.translation_layer import gdm_manager
 from local_agent.translation_layer.command_handlers import base
 from local_agent.translation_layer.command_handlers.handler_registry import GDM_CAPABILITIES_TO_COMMAND_HANDLERS
-
+from ui_automator import errors as ua_errors
+from ui_automator import ui_automator
 
 logger = logger_module.get_logger()
 
@@ -77,6 +78,9 @@
         # GDM manager
         self._mgr = gdm_manager.GdmManager(self.update_handlers_cls_map)
 
+        # UI Automator
+        self._ui_automator = ui_automator.UIAutomator()
+
         # Checks busy devices
         self._rpc_execution_lock = threading.RLock()
         self._busy_devices = set()
@@ -296,10 +300,40 @@
         pairing_code = rpc_request['params'].get('pairingCode')
         if pairing_code is None:
             raise agent_errors.InvalidRPCError(
-                'Invalid rpc command, no pairingCode in params was found.')
+                'Invalid rpc command, no pairingCode in params was found.'
+            )
+
+        device_name = rpc_request['params'].get('deviceName')
+        if device_name is None:
+            raise agent_errors.InvalidRPCError(
+                'Invalid rpc command, no deviceName in params was found.'
+            )
+
+        gha_room = rpc_request['params'].get('ghaRoom')
+        if gha_room is None:
+            raise agent_errors.InvalidRPCError(
+                'Invalid rpc command, no ghaRoom in params was found.'
+            )
 
         request_id = rpc_request['id']
-        # TODO(b/282592569): Use this pairing code to commission a matter device on GHA.
-        logger.info(f'Completed request for {request_id}, pairingCode: {pairing_code}')
+        try:
+            self._ui_automator.commission_device(
+                device_name=device_name, pairing_code=pairing_code, gha_room=gha_room
+            )
+        except (ua_errors.AndroidDeviceNotReadyError, ua_errors.MoblySnippetError) as e:
+            return {
+                'id': request_id,
+                'jsonrpc': '2.0',
+                'result': {'isCommissioned': False, 'errorLog': str(e)},
+            }
+        finally:
+            logger.info(
+                f'Completed request for {request_id}, pairingCode: {pairing_code},'
+                f' deviceName: {device_name}, ghaRoom: {gha_room}'
+            )
 
-        return {'id': request_id, 'jsonrpc': '2.0', 'result': {}}
+        return {
+            'id': request_id,
+            'jsonrpc': '2.0',
+            'result': {'isCommissioned': True},
+        }