diff --git a/android_env/components/config_classes.py b/android_env/components/config_classes.py index a3d2b41..b4ed956 100644 --- a/android_env/components/config_classes.py +++ b/android_env/components/config_classes.py @@ -36,13 +36,9 @@ class AdbControllerConfig: @dataclasses.dataclass -class CoordinatorConfig: - """Config class for Coordinator.""" +class DeviceSettingsConfig: + """Config class for DeviceSettings.""" - # Number of virtual "fingers" of the agent. - num_fingers: int = 1 - # Whether to enable keyboard key events. - enable_key_events: bool = False # Whether to show circles on the screen indicating touch position. show_touches: bool = True # Whether to show blue lines on the screen indicating touch position. @@ -51,10 +47,24 @@ class CoordinatorConfig: show_status_bar: bool = False # Whether or not to show the navigation (bottom) bar. show_navigation_bar: bool = False + + +@dataclasses.dataclass +class CoordinatorConfig: + """Config class for Coordinator.""" + + # Number of virtual "fingers" of the agent. + num_fingers: int = 1 + # Whether to enable keyboard key events. + enable_key_events: bool = False # Time between periodic restarts in minutes. If > 0, will trigger # a simulator restart at the beginning of the next episode once the time has # been reached. periodic_restart_time_min: float = 0.0 + # General Android settings. + device_settings: DeviceSettingsConfig = dataclasses.field( + default_factory=DeviceSettingsConfig + ) @dataclasses.dataclass diff --git a/android_env/components/coordinator.py b/android_env/components/coordinator.py index 8139495..162d0b1 100644 --- a/android_env/components/coordinator.py +++ b/android_env/components/coordinator.py @@ -25,6 +25,7 @@ from android_env.components import action_type as action_type_lib from android_env.components import adb_call_parser from android_env.components import config_classes +from android_env.components import device_settings as device_settings_lib from android_env.components import errors from android_env.components import pixel_fns from android_env.components import specs @@ -42,6 +43,7 @@ def __init__( self, simulator: base_simulator.BaseSimulator, task_manager: task_manager_lib.TaskManager, + device_settings: device_settings_lib.DeviceSettings, config: config_classes.CoordinatorConfig | None = None, ): """Handles communication between AndroidEnv and its components. @@ -54,12 +56,8 @@ def __init__( self._simulator = simulator self._task_manager = task_manager self._config = config or config_classes.CoordinatorConfig() + self._device_settings = device_settings self._adb_call_parser: adb_call_parser.AdbCallParser = None - self._orientation = np.zeros(4, dtype=np.uint8) - - # The size of the device screen in pixels. - self._screen_width = 0 - self._screen_height = 0 # Initialize stats. self._stats = { @@ -91,41 +89,9 @@ def action_spec(self) -> dict[str, dm_env.specs.Array]: def observation_spec(self) -> dict[str, dm_env.specs.Array]: return specs.base_observation_spec( - height=self._screen_height, width=self._screen_width - ) - - def _update_screen_size(self) -> None: - """Sets the screen size from a screenshot ignoring the color channel.""" - screenshot = self._simulator.get_screenshot() - self._screen_height = screenshot.shape[0] - self._screen_width = screenshot.shape[1] - - def _update_device_orientation(self) -> None: - """Updates the current device orientation.""" - - # Skip fetching the orientation if we already have it. - if not np.all(self._orientation == np.zeros(4)): - logging.info('self._orientation already set, not setting it again') - return - - orientation_response = self._adb_call_parser.parse( - adb_pb2.AdbRequest( - get_orientation=adb_pb2.AdbRequest.GetOrientationRequest() - ) + height=self._device_settings.screen_height(), + width=self._device_settings.screen_width(), ) - if orientation_response.status != adb_pb2.AdbResponse.Status.OK: - logging.error('Got bad orientation: %r', orientation_response) - return - - orientation = orientation_response.get_orientation.orientation - if orientation not in {0, 1, 2, 3}: - logging.error('Got bad orientation: %r', orientation_response) - return - - # Transform into one-hot format. - orientation_onehot = np.zeros([4], dtype=np.uint8) - orientation_onehot[orientation] = 1 - self._orientation = orientation_onehot def _should_periodic_relaunch(self) -> bool: """Checks if it is time to restart the simulator. @@ -178,9 +144,9 @@ def _launch_simulator(self, max_retries: int = 3): # From here on, the simulator is assumed to be up and running. self._adb_call_parser = self._create_adb_call_parser() try: - self._update_settings() + self._device_settings.update(self._config.device_settings) except errors.AdbControllerError as e: - logging.exception('_update_settings() failed.') + logging.exception('device_settings.update() failed.') self._stats['relaunch_count_update_settings'] += 1 self._latest_error = e num_tries += 1 @@ -205,51 +171,6 @@ def _launch_simulator(self, max_retries: int = 3): self._stats['relaunch_count'] += 1 break - def _update_settings(self) -> None: - """Updates some internal state and preferences given in the constructor.""" - - self._update_screen_size() - self._adb_call_parser.parse( - adb_pb2.AdbRequest( - settings=adb_pb2.AdbRequest.SettingsRequest( - name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.SYSTEM, - put=adb_pb2.AdbRequest.SettingsRequest.Put( - key='show_touches', - value='1' if self._config.show_touches else '0', - ), - ) - ) - ) - self._adb_call_parser.parse( - adb_pb2.AdbRequest( - settings=adb_pb2.AdbRequest.SettingsRequest( - name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.SYSTEM, - put=adb_pb2.AdbRequest.SettingsRequest.Put( - key='pointer_location', - value='1' if self._config.show_pointer_location else '0', - ), - ) - ) - ) - if self._config.show_navigation_bar and self._config.show_status_bar: - policy_control_value = 'null*' - elif self._config.show_navigation_bar and not self._config.show_status_bar: - policy_control_value = 'immersive.status=*' - elif not self._config.show_navigation_bar and self._config.show_status_bar: - policy_control_value = 'immersive.navigation=*' - else: - policy_control_value = 'immersive.full=*' - self._adb_call_parser.parse( - adb_pb2.AdbRequest( - settings=adb_pb2.AdbRequest.SettingsRequest( - name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.GLOBAL, - put=adb_pb2.AdbRequest.SettingsRequest.Put( - key='policy_control', value=policy_control_value - ), - ) - ) - ) - def _create_adb_call_parser(self): """Creates a new AdbCallParser instance.""" return adb_call_parser.AdbCallParser( @@ -276,8 +197,8 @@ def rl_reset(self) -> dm_env.TimeStep: if not action_fns.send_action_to_simulator( action_fns.lift_all_fingers_action(self._config.num_fingers), self._simulator, - self._screen_width, - self._screen_height, + self._device_settings.screen_width(), + self._device_settings.screen_height(), self._config.num_fingers, ): self._stats['relaunch_count_execute_action'] += 1 @@ -285,7 +206,7 @@ def rl_reset(self) -> dm_env.TimeStep: # Reset the task. self._task_manager.reset_task() - self._update_device_orientation() + self._device_settings.get_orientation() # Get data from the simulator. simulator_signals = self._gather_simulator_signals() @@ -307,8 +228,8 @@ def rl_step(self, agent_action: dict[str, np.ndarray]) -> dm_env.TimeStep: if not action_fns.send_action_to_simulator( agent_action, self._simulator, - self._screen_width, - self._screen_height, + self._device_settings.screen_width(), + self._device_settings.screen_height(), self._config.num_fingers, ): self._stats['relaunch_count_execute_action'] += 1 @@ -341,7 +262,7 @@ def _gather_simulator_signals(self) -> dict[str, np.ndarray]: return { 'pixels': self._simulator.get_screenshot(), - 'orientation': self._orientation, + 'orientation': self._device_settings.get_orientation(), 'timedelta': np.array(timestamp_delta, dtype=np.int64), } diff --git a/android_env/components/coordinator_test.py b/android_env/components/coordinator_test.py index 36e0cd8..78d3a4f 100644 --- a/android_env/components/coordinator_test.py +++ b/android_env/components/coordinator_test.py @@ -25,6 +25,7 @@ from android_env.components import adb_call_parser from android_env.components import config_classes from android_env.components import coordinator as coordinator_lib +from android_env.components import device_settings as device_settings_lib from android_env.components import errors from android_env.components import task_manager from android_env.components.simulators import base_simulator @@ -54,7 +55,9 @@ def setUp(self): autospec=True, return_value=self._adb_call_parser)) self._coordinator = coordinator_lib.Coordinator( - simulator=self._simulator, task_manager=self._task_manager + simulator=self._simulator, + task_manager=self._task_manager, + device_settings=device_settings_lib.DeviceSettings(self._simulator), ) def tearDown(self): @@ -92,6 +95,7 @@ def test_lift_all_fingers(self, unused_mock_sleep): self._coordinator = coordinator_lib.Coordinator( simulator=self._simulator, task_manager=self._task_manager, + device_settings=device_settings_lib.DeviceSettings(self._simulator), config=config_classes.CoordinatorConfig(num_fingers=3), ) self._coordinator.rl_reset() @@ -183,6 +187,7 @@ def test_execute_multitouch_action(self, unused_mock_sleep): self._coordinator = coordinator_lib.Coordinator( simulator=self._simulator, task_manager=self._task_manager, + device_settings=device_settings_lib.DeviceSettings(self._simulator), config=config_classes.CoordinatorConfig(num_fingers=3), ) @@ -273,67 +278,6 @@ def test_execute_adb_call(self, unused_mock_sleep): self.assertEqual(response, expected_response) self._adb_call_parser.parse.assert_called_with(call) - @parameterized.parameters( - (True, '1'), - (False, '0'), - ) - @mock.patch.object(time, 'sleep', autospec=True) - def test_touch_indicator(self, show, expected_value, unused_mock_sleep): - _ = coordinator_lib.Coordinator( - simulator=self._simulator, - task_manager=self._task_manager, - config=config_classes.CoordinatorConfig(show_touches=show), - ) - self._adb_call_parser.parse.assert_any_call( - adb_pb2.AdbRequest( - settings=adb_pb2.AdbRequest.SettingsRequest( - name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.SYSTEM, - put=adb_pb2.AdbRequest.SettingsRequest.Put( - key='show_touches', value=expected_value)))) - - @parameterized.parameters( - (True, '1'), - (False, '0'), - ) - @mock.patch.object(time, 'sleep', autospec=True) - def test_pointer_location(self, show, expected_value, unused_mock_sleep): - _ = coordinator_lib.Coordinator( - simulator=self._simulator, - task_manager=self._task_manager, - config=config_classes.CoordinatorConfig(show_pointer_location=show), - ) - self._adb_call_parser.parse.assert_any_call( - adb_pb2.AdbRequest( - settings=adb_pb2.AdbRequest.SettingsRequest( - name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.SYSTEM, - put=adb_pb2.AdbRequest.SettingsRequest.Put( - key='pointer_location', value=expected_value)))) - - @parameterized.parameters( - (True, True, 'null*'), - (True, False, 'immersive.status=*'), - (False, True, 'immersive.navigation=*'), - (False, False, 'immersive.full=*'), - (None, None, 'immersive.full=*'), # Defaults to hiding both. - ) - @mock.patch.object(time, 'sleep', autospec=True) - def test_bar_visibility(self, show_navigation_bar, show_status_bar, - expected_value, unused_mock_sleep): - _ = coordinator_lib.Coordinator( - simulator=self._simulator, - task_manager=self._task_manager, - config=config_classes.CoordinatorConfig( - show_navigation_bar=show_navigation_bar, - show_status_bar=show_status_bar, - ), - ) - self._adb_call_parser.parse.assert_any_call( - adb_pb2.AdbRequest( - settings=adb_pb2.AdbRequest.SettingsRequest( - name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.GLOBAL, - put=adb_pb2.AdbRequest.SettingsRequest.Put( - key='policy_control', value=expected_value)))) - if __name__ == '__main__': absltest.main() diff --git a/android_env/components/device_settings.py b/android_env/components/device_settings.py new file mode 100644 index 0000000..66245a5 --- /dev/null +++ b/android_env/components/device_settings.py @@ -0,0 +1,151 @@ +# coding=utf-8 +# Copyright 2024 DeepMind Technologies Limited. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Sets and gets some global settings on an Android device.""" + +from absl import logging +from android_env.components import adb_call_parser +from android_env.components import config_classes +from android_env.components.simulators import base_simulator +from android_env.proto import adb_pb2 +import numpy as np + + +class DeviceSettings: + """An abstraction for general properties and settings of an Android device.""" + + def __init__(self, simulator: base_simulator.BaseSimulator): + self._simulator = simulator + self._adb_call_parser = adb_call_parser.AdbCallParser( + adb_controller=self._simulator.create_adb_controller() + ) + + # The size of the device screen in pixels. + self._screen_width: int = 0 + self._screen_height: int = 0 + # The device orientation. + self._orientation = np.zeros(4, dtype=np.uint8) + + def update(self, config: config_classes.DeviceSettingsConfig) -> None: + """Sets the configuration of the device according to `config`.""" + + self._update_screen_size() + self._set_show_touches(config.show_touches) + self._set_show_pointer_location(config.show_pointer_location) + self._set_status_navigation_bars( + config.show_navigation_bar, config.show_status_bar + ) + + def screen_width(self) -> int: + """The screen width in pixels. Only valid after `update()` is called.""" + + return self._screen_width + + def screen_height(self) -> int: + """The screen height in pixels. Only valid after `update()` is called.""" + + return self._screen_height + + def get_orientation(self) -> np.ndarray: + """Returns the device orientation. Please see specs.py for details.""" + + self._update_orientation() + return self._orientation + + def _update_screen_size(self) -> None: + """Sets the screen size from a screenshot ignoring the color channel.""" + + screenshot = self._simulator.get_screenshot() + self._screen_height = screenshot.shape[0] + self._screen_width = screenshot.shape[1] + + def _set_show_touches(self, show: bool) -> None: + """Whether to display circles indicating the touch position.""" + + self._adb_call_parser.parse( + adb_pb2.AdbRequest( + settings=adb_pb2.AdbRequest.SettingsRequest( + name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.SYSTEM, + put=adb_pb2.AdbRequest.SettingsRequest.Put( + key='show_touches', value='1' if show else '0' + ), + ) + ) + ) + + def _set_show_pointer_location(self, show: bool) -> None: + """Whether to display blue lines on the screen indicating touch position.""" + + self._adb_call_parser.parse( + adb_pb2.AdbRequest( + settings=adb_pb2.AdbRequest.SettingsRequest( + name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.SYSTEM, + put=adb_pb2.AdbRequest.SettingsRequest.Put( + key='pointer_location', value='1' if show else '0' + ), + ) + ) + ) + + def _set_status_navigation_bars( + self, show_navigation: bool, show_status: bool + ) -> None: + """Whether to display the status (top) and navigation (bottom) bars.""" + + if show_navigation and show_status: + policy_control_value = 'null*' + elif show_navigation and not show_status: + policy_control_value = 'immersive.status=*' + elif not show_navigation and show_status: + policy_control_value = 'immersive.navigation=*' + else: + policy_control_value = 'immersive.full=*' + + self._adb_call_parser.parse( + adb_pb2.AdbRequest( + settings=adb_pb2.AdbRequest.SettingsRequest( + name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.GLOBAL, + put=adb_pb2.AdbRequest.SettingsRequest.Put( + key='policy_control', value=policy_control_value + ), + ) + ) + ) + + def _update_orientation(self) -> None: + """Updates the current device orientation.""" + + # Skip fetching the orientation if we already have it. + if not np.all(self._orientation == np.zeros(4)): + return + + orientation_response = self._adb_call_parser.parse( + adb_pb2.AdbRequest( + get_orientation=adb_pb2.AdbRequest.GetOrientationRequest() + ) + ) + if orientation_response.status != adb_pb2.AdbResponse.Status.OK: + logging.error('Got bad orientation: %r', orientation_response) + return + + orientation = orientation_response.get_orientation.orientation + if orientation not in {0, 1, 2, 3}: + logging.error('Got bad orientation: %r', orientation) + return + + # Transform into one-hot format. + orientation_onehot = np.zeros([4], dtype=np.uint8) + orientation_onehot[orientation] = 1 + self._orientation = orientation_onehot diff --git a/android_env/components/device_settings_test.py b/android_env/components/device_settings_test.py new file mode 100644 index 0000000..83eb5f5 --- /dev/null +++ b/android_env/components/device_settings_test.py @@ -0,0 +1,228 @@ +# coding=utf-8 +# Copyright 2024 DeepMind Technologies Limited. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import mock + +from absl.testing import absltest +from absl.testing import parameterized +from android_env.components import config_classes +from android_env.components import device_settings as device_settings_lib +from android_env.components.simulators import base_simulator +import numpy as np + + +class DeviceSettingsTest(parameterized.TestCase): + + def test_screen_size_before_update(self): + """The screen size should be 0x0 without calling `update()`.""" + + # Arrange. + simulator = mock.create_autospec(base_simulator.BaseSimulator) + device_settings = device_settings_lib.DeviceSettings(simulator) + + # Act. + height = device_settings.screen_height() + width = device_settings.screen_width() + + # Assert. + self.assertEqual(height, 0) + self.assertEqual(width, 0) + + def test_screen_size_after_update(self): + """The screen size should be set after calling `update()`.""" + + # Arrange. + simulator = mock.create_autospec(base_simulator.BaseSimulator) + simulator.get_screenshot.return_value = np.random.randint( + low=0, high=255, size=(123, 456, 3), dtype=np.uint8 + ) + adb_controller = simulator.create_adb_controller.return_value + adb_controller.execute_command.return_value = b'' + device_settings = device_settings_lib.DeviceSettings(simulator) + + # Act. + device_settings.update(config_classes.DeviceSettingsConfig()) + height = device_settings.screen_height() + width = device_settings.screen_width() + + # Assert. + self.assertEqual(height, 123) + self.assertEqual(width, 456) + + @parameterized.named_parameters( + ( + 'show_touches', + config_classes.DeviceSettingsConfig(show_touches=True), + mock.call( + ['shell', 'settings', 'put', 'system', 'show_touches', '1'], + timeout=None, + ), + ), + ( + 'show_touches_false', + config_classes.DeviceSettingsConfig(show_touches=False), + mock.call( + ['shell', 'settings', 'put', 'system', 'show_touches', '0'], + timeout=None, + ), + ), + ( + 'show_pointer_location', + config_classes.DeviceSettingsConfig(show_pointer_location=True), + mock.call( + ['shell', 'settings', 'put', 'system', 'pointer_location', '1'], + timeout=None, + ), + ), + ( + 'show_pointer_location_false', + config_classes.DeviceSettingsConfig(show_pointer_location=False), + mock.call( + ['shell', 'settings', 'put', 'system', 'pointer_location', '0'], + timeout=None, + ), + ), + ( + 'show_navigation_and_status', + config_classes.DeviceSettingsConfig( + show_navigation_bar=True, show_status_bar=True + ), + mock.call( + ['shell', 'settings', 'put', 'global', 'policy_control', 'null*'], + timeout=None, + ), + ), + ( + 'show_navigation_and_no_status', + config_classes.DeviceSettingsConfig( + show_navigation_bar=True, show_status_bar=False + ), + mock.call( + [ + 'shell', + 'settings', + 'put', + 'global', + 'policy_control', + 'immersive.status=*', + ], + timeout=None, + ), + ), + ( + 'show_no_navigation_and_status', + config_classes.DeviceSettingsConfig( + show_navigation_bar=False, show_status_bar=True + ), + mock.call( + [ + 'shell', + 'settings', + 'put', + 'global', + 'policy_control', + 'immersive.navigation=*', + ], + timeout=None, + ), + ), + ( + 'show_no_navigation_and_no_status', + config_classes.DeviceSettingsConfig( + show_navigation_bar=False, show_status_bar=False + ), + mock.call( + [ + 'shell', + 'settings', + 'put', + 'global', + 'policy_control', + 'immersive.full=*', + ], + timeout=None, + ), + ), + ) + def test_update( + self, settings: config_classes.DeviceSettingsConfig, expected_call + ): + """We expect the right call for each setting.""" + + # Arrange. + simulator = mock.create_autospec(base_simulator.BaseSimulator) + adb_controller = simulator.create_adb_controller.return_value + adb_controller.execute_command.return_value = b'' + device_settings = device_settings_lib.DeviceSettings(simulator) + + # Act. + device_settings.update(settings) + + # Assert. + adb_controller.execute_command.assert_has_calls( + [expected_call], any_order=True + ) + + def test_get_orientation_bad_response(self): + """The orientation should be unset if the underlying response is bad.""" + + # Arrange. + simulator = mock.create_autospec(base_simulator.BaseSimulator) + adb_controller = simulator.create_adb_controller.return_value + adb_controller.execute_command.return_value = b'' + device_settings = device_settings_lib.DeviceSettings(simulator) + + # Act. + orientation = device_settings.get_orientation() + + # Assert. + np.testing.assert_array_equal(orientation, np.zeros(4)) + + def test_get_orientation_bad_orientation(self): + """The orientation should be unset if the underlying orientation is bad.""" + + # Arrange. + simulator = mock.create_autospec(base_simulator.BaseSimulator) + adb_controller = simulator.create_adb_controller.return_value + adb_controller.execute_command.return_value = b' InputDeviceOrientation: 9' + device_settings = device_settings_lib.DeviceSettings(simulator) + + # Act. + orientation = device_settings.get_orientation() + + # Assert. + np.testing.assert_array_equal(orientation, np.zeros(4)) + + def test_get_orientation_success(self): + """Checks that the orientation comes back as expected.""" + + # Arrange. + simulator = mock.create_autospec(base_simulator.BaseSimulator) + adb_controller = simulator.create_adb_controller.return_value + adb_controller.execute_command.return_value = b' InputDeviceOrientation: 3' + device_settings = device_settings_lib.DeviceSettings(simulator) + + # Act. + orientation = device_settings.get_orientation() + # The output should be idempotent if the underlying system did not change. + orientation_again = device_settings.get_orientation() + + # Assert. + np.testing.assert_array_equal(orientation, np.array([0, 0, 0, 1])) + np.testing.assert_array_equal(orientation, orientation_again) + + +if __name__ == '__main__': + absltest.main() diff --git a/android_env/loader.py b/android_env/loader.py index c3c5601..0588ff3 100644 --- a/android_env/loader.py +++ b/android_env/loader.py @@ -21,6 +21,7 @@ from android_env import environment from android_env.components import config_classes from android_env.components import coordinator as coordinator_lib +from android_env.components import device_settings as device_settings_lib from android_env.components import task_manager as task_manager_lib from android_env.components.simulators.emulator import emulator_simulator from android_env.components.simulators.fake import fake_simulator @@ -58,7 +59,10 @@ def load(config: config_classes.AndroidEnvConfig) -> environment.AndroidEnv: case _: raise ValueError('Unsupported simulator config: {config.simulator}') - coordinator = coordinator_lib.Coordinator(simulator, task_manager) + device_settings = device_settings_lib.DeviceSettings(simulator) + coordinator = coordinator_lib.Coordinator( + simulator, task_manager, device_settings + ) return environment.AndroidEnv( simulator=simulator, coordinator=coordinator, task_manager=task_manager ) diff --git a/android_env/loader_test.py b/android_env/loader_test.py index 9ca2a85..280fa4a 100644 --- a/android_env/loader_test.py +++ b/android_env/loader_test.py @@ -24,6 +24,7 @@ from android_env import loader from android_env.components import config_classes from android_env.components import coordinator as coordinator_lib +from android_env.components import device_settings as device_settings_lib from android_env.components import task_manager as task_manager_lib from android_env.components.simulators.emulator import emulator_simulator from android_env.components.simulators.fake import fake_simulator @@ -34,10 +35,16 @@ class LoaderTest(absltest.TestCase): @mock.patch.object(task_manager_lib, 'TaskManager', autospec=True) @mock.patch.object(emulator_simulator, 'EmulatorSimulator', autospec=True) + @mock.patch.object(device_settings_lib, 'DeviceSettings', autospec=True) @mock.patch.object(coordinator_lib, 'Coordinator', autospec=True) @mock.patch.object(builtins, 'open', autospec=True) def test_load_emulator( - self, mock_open, mock_coordinator, mock_simulator_class, mock_task_manager + self, + mock_open, + mock_coordinator, + mock_device_settings, + mock_simulator_class, + mock_task_manager, ): # Arrange. @@ -85,14 +92,21 @@ def test_load_emulator( mock_coordinator.assert_called_with( mock_simulator_class.return_value, mock_task_manager.return_value, + mock_device_settings.return_value, ) @mock.patch.object(task_manager_lib, 'TaskManager', autospec=True) @mock.patch.object(fake_simulator, 'FakeSimulator', autospec=True) + @mock.patch.object(device_settings_lib, 'DeviceSettings', autospec=True) @mock.patch.object(coordinator_lib, 'Coordinator', autospec=True) @mock.patch.object(builtins, 'open', autospec=True) def test_load_fake_simulator( - self, mock_open, mock_coordinator, mock_simulator_class, mock_task_manager + self, + mock_open, + mock_coordinator, + mock_device_settings, + mock_simulator_class, + mock_task_manager, ): # Arrange. @@ -118,6 +132,7 @@ def test_load_fake_simulator( mock_coordinator.assert_called_with( mock_simulator_class.return_value, mock_task_manager.return_value, + mock_device_settings.return_value, ) @mock.patch.object(task_manager_lib, 'TaskManager', autospec=True)