[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},
+ }