android-env 1.2.2__py3-none-any.whl → 1.2.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. android_env/__init__.py +1 -1
  2. android_env/components/__init__.py +1 -1
  3. android_env/components/a11y/__init__.py +15 -0
  4. android_env/components/a11y/a11y_events.py +118 -0
  5. android_env/components/a11y/a11y_events_test.py +173 -0
  6. android_env/components/a11y/a11y_forests.py +128 -0
  7. android_env/components/a11y/a11y_forests_test.py +237 -0
  8. android_env/components/a11y/a11y_servicer.py +199 -0
  9. android_env/components/a11y/a11y_servicer_test.py +224 -0
  10. android_env/components/action_fns.py +132 -0
  11. android_env/components/action_fns_test.py +227 -0
  12. android_env/components/action_type.py +26 -3
  13. android_env/components/adb_call_parser.py +233 -185
  14. android_env/components/adb_call_parser_test.py +165 -163
  15. android_env/components/adb_controller.py +19 -28
  16. android_env/components/adb_controller_test.py +100 -9
  17. android_env/components/adb_log_stream.py +3 -3
  18. android_env/components/adb_log_stream_test.py +1 -1
  19. android_env/components/app_screen_checker.py +15 -13
  20. android_env/components/app_screen_checker_test.py +1 -1
  21. android_env/components/config_classes.py +203 -0
  22. android_env/components/coordinator.py +53 -338
  23. android_env/components/coordinator_test.py +26 -283
  24. android_env/components/device_settings.py +174 -0
  25. android_env/components/device_settings_test.py +228 -0
  26. android_env/components/dumpsys_thread.py +3 -4
  27. android_env/components/dumpsys_thread_test.py +1 -1
  28. android_env/components/errors.py +2 -5
  29. android_env/components/errors_test.py +1 -1
  30. android_env/components/log_stream.py +2 -2
  31. android_env/components/log_stream_test.py +1 -1
  32. android_env/components/logcat_thread.py +9 -8
  33. android_env/components/logcat_thread_test.py +2 -3
  34. android_env/components/{utils.py → pixel_fns.py} +19 -20
  35. android_env/components/{utils_test.py → pixel_fns_test.py} +20 -15
  36. android_env/components/setup_step_interpreter.py +45 -37
  37. android_env/components/setup_step_interpreter_test.py +1 -1
  38. android_env/components/simulators/__init__.py +1 -1
  39. android_env/components/simulators/base_simulator.py +79 -23
  40. android_env/components/simulators/base_simulator_test.py +131 -9
  41. android_env/components/simulators/emulator/__init__.py +1 -1
  42. android_env/components/simulators/emulator/emulator_launcher.py +62 -81
  43. android_env/components/simulators/emulator/emulator_launcher_test.py +120 -43
  44. android_env/components/simulators/emulator/emulator_simulator.py +111 -98
  45. android_env/components/simulators/emulator/emulator_simulator_test.py +174 -138
  46. android_env/components/simulators/fake/__init__.py +1 -1
  47. android_env/components/simulators/fake/fake_simulator.py +9 -17
  48. android_env/components/simulators/fake/fake_simulator_test.py +23 -8
  49. android_env/components/specs.py +1 -1
  50. android_env/components/specs_test.py +1 -1
  51. android_env/components/task_manager.py +26 -31
  52. android_env/components/task_manager_test.py +1 -18
  53. android_env/env_interface.py +1 -17
  54. android_env/environment.py +27 -17
  55. android_env/environment_test.py +51 -25
  56. android_env/loader.py +57 -43
  57. android_env/loader_test.py +115 -35
  58. android_env/proto/__init__.py +1 -1
  59. android_env/proto/a11y/__init__.py +15 -0
  60. android_env/proto/a11y/a11y.proto +75 -0
  61. android_env/proto/a11y/a11y_pb2.py +54 -0
  62. android_env/proto/a11y/a11y_pb2.pyi +49 -0
  63. android_env/proto/a11y/a11y_pb2_grpc.py +202 -0
  64. android_env/proto/a11y/android_accessibility_action.proto +32 -0
  65. android_env/proto/a11y/android_accessibility_action_pb2.py +37 -0
  66. android_env/proto/a11y/android_accessibility_action_pb2.pyi +13 -0
  67. android_env/proto/a11y/android_accessibility_action_pb2_grpc.py +24 -0
  68. android_env/proto/a11y/android_accessibility_forest.proto +29 -0
  69. android_env/proto/a11y/android_accessibility_forest_pb2.py +38 -0
  70. android_env/proto/a11y/android_accessibility_forest_pb2.pyi +13 -0
  71. android_env/proto/a11y/android_accessibility_forest_pb2_grpc.py +24 -0
  72. android_env/proto/a11y/android_accessibility_node_info.proto +122 -0
  73. android_env/proto/a11y/android_accessibility_node_info_clickable_span.proto +49 -0
  74. android_env/proto/a11y/android_accessibility_node_info_clickable_span_pb2.py +39 -0
  75. android_env/proto/a11y/android_accessibility_node_info_clickable_span_pb2.pyi +28 -0
  76. android_env/proto/a11y/android_accessibility_node_info_clickable_span_pb2_grpc.py +24 -0
  77. android_env/proto/a11y/android_accessibility_node_info_pb2.py +42 -0
  78. android_env/proto/a11y/android_accessibility_node_info_pb2.pyi +75 -0
  79. android_env/proto/a11y/android_accessibility_node_info_pb2_grpc.py +24 -0
  80. android_env/proto/a11y/android_accessibility_tree.proto +29 -0
  81. android_env/proto/a11y/android_accessibility_tree_pb2.py +38 -0
  82. android_env/proto/a11y/android_accessibility_tree_pb2.pyi +13 -0
  83. android_env/proto/a11y/android_accessibility_tree_pb2_grpc.py +24 -0
  84. android_env/proto/a11y/android_accessibility_window_info.proto +84 -0
  85. android_env/proto/a11y/android_accessibility_window_info_pb2.py +41 -0
  86. android_env/proto/a11y/android_accessibility_window_info_pb2.pyi +48 -0
  87. android_env/proto/a11y/android_accessibility_window_info_pb2_grpc.py +24 -0
  88. android_env/proto/a11y/rect.proto +30 -0
  89. android_env/proto/a11y/rect_pb2.py +37 -0
  90. android_env/proto/a11y/rect_pb2.pyi +17 -0
  91. android_env/proto/a11y/rect_pb2_grpc.py +24 -0
  92. android_env/proto/adb.proto +13 -1
  93. android_env/proto/adb_pb2.py +120 -107
  94. android_env/proto/adb_pb2.pyi +396 -0
  95. android_env/proto/adb_pb2_grpc.py +20 -0
  96. android_env/proto/emulator_controller.proto +1 -1
  97. android_env/proto/emulator_controller_pb2.py +142 -131
  98. android_env/proto/emulator_controller_pb2.pyi +672 -0
  99. android_env/proto/emulator_controller_pb2_grpc.py +497 -136
  100. android_env/proto/snapshot.proto +1 -1
  101. android_env/proto/snapshot_pb2.py +30 -19
  102. android_env/proto/snapshot_pb2.pyi +117 -0
  103. android_env/proto/snapshot_pb2_grpc.py +20 -0
  104. android_env/proto/snapshot_service.proto +1 -1
  105. android_env/proto/snapshot_service_pb2.py +36 -25
  106. android_env/proto/snapshot_service_pb2.pyi +86 -0
  107. android_env/proto/snapshot_service_pb2_grpc.py +119 -28
  108. android_env/proto/state.proto +1 -1
  109. android_env/proto/state_pb2.py +46 -35
  110. android_env/proto/state_pb2.pyi +85 -0
  111. android_env/proto/state_pb2_grpc.py +20 -0
  112. android_env/proto/task.proto +4 -1
  113. android_env/proto/task_pb2.py +41 -30
  114. android_env/proto/task_pb2.pyi +160 -0
  115. android_env/proto/task_pb2_grpc.py +20 -0
  116. android_env/wrappers/__init__.py +1 -1
  117. android_env/wrappers/a11y_grpc_wrapper.py +500 -0
  118. android_env/wrappers/a11y_grpc_wrapper_test.py +849 -0
  119. android_env/wrappers/base_wrapper.py +1 -5
  120. android_env/wrappers/base_wrapper_test.py +1 -7
  121. android_env/wrappers/discrete_action_wrapper.py +15 -14
  122. android_env/wrappers/discrete_action_wrapper_test.py +1 -1
  123. android_env/wrappers/flat_interface_wrapper.py +5 -5
  124. android_env/wrappers/flat_interface_wrapper_test.py +1 -1
  125. android_env/wrappers/float_pixels_wrapper.py +5 -4
  126. android_env/wrappers/float_pixels_wrapper_test.py +1 -1
  127. android_env/wrappers/gym_wrapper.py +1 -1
  128. android_env/wrappers/gym_wrapper_test.py +1 -1
  129. android_env/wrappers/image_rescale_wrapper.py +13 -10
  130. android_env/wrappers/image_rescale_wrapper_test.py +1 -1
  131. android_env/wrappers/last_action_wrapper.py +5 -4
  132. android_env/wrappers/last_action_wrapper_test.py +1 -1
  133. android_env/wrappers/rate_limit_wrapper.py +1 -1
  134. android_env/wrappers/rate_limit_wrapper_test.py +1 -1
  135. android_env/wrappers/tap_action_wrapper.py +12 -12
  136. android_env/wrappers/tap_action_wrapper_test.py +49 -14
  137. {android_env-1.2.2.dist-info → android_env-1.2.3.dist-info}/METADATA +14 -16
  138. android_env-1.2.3.dist-info/RECORD +141 -0
  139. {android_env-1.2.2.dist-info → android_env-1.2.3.dist-info}/WHEEL +1 -1
  140. android_env-1.2.2.dist-info/RECORD +0 -88
  141. {android_env-1.2.2.dist-info → android_env-1.2.3.dist-info/licenses}/LICENSE +0 -0
  142. {android_env-1.2.2.dist-info → android_env-1.2.3.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  # coding=utf-8
2
- # Copyright 2023 DeepMind Technologies Limited.
2
+ # Copyright 2024 DeepMind Technologies Limited.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -23,7 +23,9 @@ from absl.testing import absltest
23
23
  from absl.testing import parameterized
24
24
  from android_env.components import action_type
25
25
  from android_env.components import adb_call_parser
26
+ from android_env.components import config_classes
26
27
  from android_env.components import coordinator as coordinator_lib
28
+ from android_env.components import device_settings as device_settings_lib
27
29
  from android_env.components import errors
28
30
  from android_env.components import task_manager
29
31
  from android_env.components.simulators import base_simulator
@@ -34,15 +36,6 @@ import dm_env
34
36
  import numpy as np
35
37
 
36
38
 
37
- class MockScreenshotGetter:
38
- def __init__(self):
39
- self._screenshot_index = 0
40
-
41
- def get_screenshot(self):
42
- self._screenshot_index += 1
43
- return np.array(self._screenshot_index, ndmin=3)
44
-
45
-
46
39
  class CoordinatorTest(parameterized.TestCase):
47
40
 
48
41
  def setUp(self):
@@ -64,8 +57,8 @@ class CoordinatorTest(parameterized.TestCase):
64
57
  self._coordinator = coordinator_lib.Coordinator(
65
58
  simulator=self._simulator,
66
59
  task_manager=self._task_manager,
67
- num_fingers=1,
68
- periodic_restart_time_min=0)
60
+ device_settings=device_settings_lib.DeviceSettings(self._simulator),
61
+ )
69
62
 
70
63
  def tearDown(self):
71
64
  super().tearDown()
@@ -80,15 +73,31 @@ class CoordinatorTest(parameterized.TestCase):
80
73
 
81
74
  @mock.patch.object(time, 'sleep', autospec=True)
82
75
  def test_reset(self, unused_mock_sleep):
76
+ """'relaunch_count_execute_action' should be zero if there's no error."""
77
+
78
+ self._coordinator.rl_reset()
79
+ stats = self._coordinator.stats()
80
+ self.assertIn('relaunch_count_execute_action', stats)
81
+ self.assertEqual(stats['relaunch_count_execute_action'], 0)
82
+
83
+ @mock.patch.object(time, 'sleep', autospec=True)
84
+ def test_reset_error_sending_action(self, unused_mock_sleep):
85
+ """'relaunch_count_execute_action' should be positive if there's an error."""
86
+
87
+ self._simulator.send_touch.side_effect = errors.SendActionError()
83
88
  self._coordinator.rl_reset()
89
+ stats = self._coordinator.stats()
90
+ self.assertIn('relaunch_count_execute_action', stats)
91
+ self.assertEqual(stats['relaunch_count_execute_action'], 1)
84
92
 
85
93
  @mock.patch.object(time, 'sleep', autospec=True)
86
94
  def test_lift_all_fingers(self, unused_mock_sleep):
87
95
  self._coordinator = coordinator_lib.Coordinator(
88
96
  simulator=self._simulator,
89
97
  task_manager=self._task_manager,
90
- num_fingers=3,
91
- periodic_restart_time_min=0)
98
+ device_settings=device_settings_lib.DeviceSettings(self._simulator),
99
+ config=config_classes.CoordinatorConfig(num_fingers=3),
100
+ )
92
101
  self._coordinator.rl_reset()
93
102
  expected_actions = [
94
103
  # (x, y, is_down, identifier).
@@ -147,136 +156,6 @@ class CoordinatorTest(parameterized.TestCase):
147
156
  self.assertEqual(timestep.reward, 0.0)
148
157
  self.assertTrue(timestep.last())
149
158
 
150
- @mock.patch.object(time, 'sleep', autospec=True)
151
- def test_process_action_error_async(self, unused_mock_sleep):
152
- mock_interaction_thread = mock.create_autospec(
153
- coordinator_lib.InteractionThread)
154
- with mock.patch.object(
155
- coordinator_lib,
156
- 'InteractionThread',
157
- autospec=True,
158
- return_value=mock_interaction_thread):
159
- coordinator = coordinator_lib.Coordinator(
160
- simulator=self._simulator,
161
- task_manager=self._task_manager,
162
- num_fingers=1,
163
- interaction_rate_sec=0.016)
164
-
165
- def fake_rl_step(agent_action, simulator_signals):
166
- del agent_action
167
- self.assertFalse(simulator_signals['simulator_healthy'])
168
- return dm_env.truncation(reward=0.0, observation=None)
169
-
170
- self._task_manager.rl_step.side_effect = fake_rl_step
171
- mock_interaction_thread.screenshot.side_effect = errors.ReadObservationError(
172
- )
173
- timestep = coordinator.rl_step(
174
- agent_action={
175
- 'action_type': np.array(action_type.ActionType.LIFT),
176
- 'touch_position': np.array([0.5, 0.5]),
177
- })
178
- self.assertIsNone(timestep.observation)
179
- self.assertEqual(timestep.reward, 0.0)
180
- self.assertTrue(timestep.last())
181
- coordinator.close()
182
-
183
- def test_async_step_faster_than_screenshot(self):
184
- """Return same screenshot when step is faster than the interaction rate."""
185
- screenshot_getter = MockScreenshotGetter()
186
- self._simulator.get_screenshot.side_effect = screenshot_getter.get_screenshot
187
- def fake_rl_step(simulator_signals):
188
- return dm_env.transition(
189
- reward=10.0,
190
- observation={
191
- 'pixels': simulator_signals['pixels'],
192
- 'orientation': simulator_signals['orientation'],
193
- 'timedelta': simulator_signals['timedelta'],
194
- 'extras': {
195
- 'extra': [0.0]
196
- }
197
- })
198
- self._task_manager.rl_step.side_effect = fake_rl_step
199
- coordinator = coordinator_lib.Coordinator(
200
- simulator=self._simulator,
201
- task_manager=self._task_manager,
202
- num_fingers=1,
203
- interaction_rate_sec=0.5)
204
- ts1 = coordinator.rl_step(
205
- agent_action={
206
- 'action_type': np.array(action_type.ActionType.LIFT),
207
- 'touch_position': np.array([0.5, 0.5]),
208
- })
209
- ts2 = coordinator.rl_step(
210
- agent_action={
211
- 'action_type': np.array(action_type.ActionType.LIFT),
212
- 'touch_position': np.array([0.5, 0.5]),
213
- })
214
- np.testing.assert_almost_equal(ts2.observation['pixels'],
215
- ts1.observation['pixels'])
216
- coordinator.close()
217
-
218
- def test_async_step_slower_than_screenshot(self):
219
- """Return different screenshots when step slower than the interaction rate."""
220
- screenshot_getter = MockScreenshotGetter()
221
- self._simulator.get_screenshot.side_effect = screenshot_getter.get_screenshot
222
-
223
- def fake_rl_step(simulator_signals):
224
- return dm_env.transition(
225
- reward=10.0,
226
- observation={
227
- 'pixels': simulator_signals['pixels'],
228
- 'orientation': simulator_signals['orientation'],
229
- 'timedelta': simulator_signals['timedelta'],
230
- 'extras': {
231
- 'extra': [0.0]
232
- }
233
- })
234
-
235
- self._task_manager.rl_step.side_effect = fake_rl_step
236
- coordinator = coordinator_lib.Coordinator(
237
- simulator=self._simulator,
238
- task_manager=self._task_manager,
239
- num_fingers=1,
240
- interaction_rate_sec=0.01)
241
- ts1 = coordinator.rl_step(
242
- agent_action={
243
- 'action_type': np.array(action_type.ActionType.LIFT),
244
- 'touch_position': np.array([0.5, 0.5]),
245
- })
246
- time.sleep(0.5)
247
- ts2 = coordinator.rl_step(
248
- agent_action={
249
- 'action_type': np.array(action_type.ActionType.LIFT),
250
- 'touch_position': np.array([0.5, 0.5]),
251
- })
252
- np.testing.assert_raises(AssertionError, np.testing.assert_array_equal,
253
- ts2.observation['pixels'],
254
- ts1.observation['pixels'])
255
- coordinator.close()
256
-
257
- def test_interaction_thread_closes_upon_relaunch(self):
258
- """Async coordinator should kill the InteractionThread when relaunching."""
259
- mock_interaction_thread = mock.create_autospec(
260
- coordinator_lib.InteractionThread)
261
- with mock.patch.object(
262
- coordinator_lib,
263
- 'InteractionThread',
264
- autospec=True,
265
- return_value=mock_interaction_thread):
266
- coordinator = coordinator_lib.Coordinator(
267
- simulator=self._simulator,
268
- task_manager=self._task_manager,
269
- num_fingers=1,
270
- periodic_restart_time_min=1E-6,
271
- interaction_rate_sec=0.5)
272
- mock_interaction_thread.stop.assert_not_called()
273
- mock_interaction_thread.join.assert_not_called()
274
- time.sleep(0.1)
275
- coordinator.rl_reset()
276
- mock_interaction_thread.stop.assert_called_once()
277
- mock_interaction_thread.join.assert_called_once()
278
- coordinator.close()
279
-
280
159
  @mock.patch.object(time, 'sleep', autospec=True)
281
160
  def test_execute_action_touch(self, unused_mock_sleep):
282
161
 
@@ -308,8 +187,9 @@ class CoordinatorTest(parameterized.TestCase):
308
187
  self._coordinator = coordinator_lib.Coordinator(
309
188
  simulator=self._simulator,
310
189
  task_manager=self._task_manager,
311
- num_fingers=3,
312
- periodic_restart_time_min=0)
190
+ device_settings=device_settings_lib.DeviceSettings(self._simulator),
191
+ config=config_classes.CoordinatorConfig(num_fingers=3),
192
+ )
313
193
 
314
194
  def fake_rl_step(simulator_signals):
315
195
  return dm_env.transition(
@@ -398,143 +278,6 @@ class CoordinatorTest(parameterized.TestCase):
398
278
  self.assertEqual(response, expected_response)
399
279
  self._adb_call_parser.parse.assert_called_with(call)
400
280
 
401
- @mock.patch.object(time, 'sleep', autospec=True)
402
- @mock.patch.object(tempfile, 'gettempdir', autospec=True)
403
- def test_with_tmp_dir_no_tempfile_call(self, mock_gettempdir,
404
- unused_mock_sleep):
405
- """If passing a `tmp_dir`, `tempfile.gettempdir()` should not be called."""
406
- _ = coordinator_lib.Coordinator(
407
- simulator=self._simulator,
408
- task_manager=self._task_manager,
409
- periodic_restart_time_min=0,
410
- tmp_dir=absltest.get_default_test_tmpdir())
411
- mock_gettempdir.assert_not_called()
412
-
413
- @mock.patch.object(time, 'sleep', autospec=True)
414
- @mock.patch.object(tempfile, 'gettempdir', autospec=True)
415
- def test_no_tmp_dir_calls_tempfile(self, mock_gettempdir, unused_mock_sleep):
416
- """If not passing a `tmp_dir`, `tempfile.gettempdir()` should be called."""
417
- _ = coordinator_lib.Coordinator(
418
- simulator=self._simulator,
419
- task_manager=self._task_manager,
420
- periodic_restart_time_min=0)
421
- mock_gettempdir.assert_called_once()
422
-
423
- @parameterized.parameters(
424
- (True, '1'),
425
- (False, '0'),
426
- )
427
- @mock.patch.object(time, 'sleep', autospec=True)
428
- def test_touch_indicator(self, show, expected_value, unused_mock_sleep):
429
- _ = coordinator_lib.Coordinator(
430
- simulator=self._simulator,
431
- task_manager=self._task_manager,
432
- show_touches=show)
433
- self._adb_call_parser.parse.assert_any_call(
434
- adb_pb2.AdbRequest(
435
- settings=adb_pb2.AdbRequest.SettingsRequest(
436
- name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.SYSTEM,
437
- put=adb_pb2.AdbRequest.SettingsRequest.Put(
438
- key='show_touches', value=expected_value))))
439
-
440
- @parameterized.parameters(
441
- (True, '1'),
442
- (False, '0'),
443
- )
444
- @mock.patch.object(time, 'sleep', autospec=True)
445
- def test_pointer_location(self, show, expected_value, unused_mock_sleep):
446
- _ = coordinator_lib.Coordinator(
447
- simulator=self._simulator,
448
- task_manager=self._task_manager,
449
- show_pointer_location=show)
450
- self._adb_call_parser.parse.assert_any_call(
451
- adb_pb2.AdbRequest(
452
- settings=adb_pb2.AdbRequest.SettingsRequest(
453
- name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.SYSTEM,
454
- put=adb_pb2.AdbRequest.SettingsRequest.Put(
455
- key='pointer_location', value=expected_value))))
456
-
457
- @parameterized.parameters(
458
- (True, True, 'null*'),
459
- (True, False, 'immersive.status=*'),
460
- (False, True, 'immersive.navigation=*'),
461
- (False, False, 'immersive.full=*'),
462
- (None, None, 'immersive.full=*'), # Defaults to hiding both.
463
- )
464
- @mock.patch.object(time, 'sleep', autospec=True)
465
- def test_bar_visibility(self, show_navigation_bar, show_status_bar,
466
- expected_value, unused_mock_sleep):
467
- _ = coordinator_lib.Coordinator(
468
- simulator=self._simulator,
469
- task_manager=self._task_manager,
470
- show_navigation_bar=show_navigation_bar,
471
- show_status_bar=show_status_bar)
472
- self._adb_call_parser.parse.assert_any_call(
473
- adb_pb2.AdbRequest(
474
- settings=adb_pb2.AdbRequest.SettingsRequest(
475
- name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.GLOBAL,
476
- put=adb_pb2.AdbRequest.SettingsRequest.Put(
477
- key='policy_control', value=expected_value))))
478
-
479
- def test_load_state(self):
480
- expected_response = state_pb2.LoadStateResponse(
481
- status=state_pb2.LoadStateResponse.Status.OK
482
- )
483
- request = state_pb2.LoadStateRequest(args={'foo': 'bar'})
484
- self._simulator.load_state.return_value = expected_response
485
- stop_call_count = self._task_manager.stop.call_count
486
- start_call_count = self._task_manager.start.call_count
487
- response = self._coordinator.load_state(request)
488
- self.assertEqual(response, expected_response)
489
- self._simulator.load_state.assert_called_once_with(request)
490
- self.assertEqual(self._task_manager.stop.call_count, stop_call_count + 1)
491
- self.assertEqual(self._task_manager.start.call_count, start_call_count + 1)
492
-
493
- def test_save_state(self):
494
- expected_response = state_pb2.SaveStateResponse(
495
- status=state_pb2.SaveStateResponse.Status.OK
496
- )
497
- request = state_pb2.SaveStateRequest(args={'foo': 'bar'})
498
- self._simulator.save_state.return_value = expected_response
499
- response = self._coordinator.save_state(request)
500
- self.assertEqual(response, expected_response)
501
- self._simulator.save_state.assert_called_once_with(request)
502
-
503
- @mock.patch.object(time, 'sleep', autospec=True)
504
- def test_update_task_succeeds(self, unused_mock_sleep):
505
- task = task_pb2.Task(id='my_task')
506
- stop_call_count = self._task_manager.stop.call_count
507
- start_call_count = self._task_manager.start.call_count
508
- setup_call_count = self._task_manager.setup_task.call_count
509
- success = self._coordinator.update_task(task)
510
- self.assertEqual(1,
511
- self._task_manager.stop.call_count - stop_call_count)
512
- self.assertEqual(
513
- 1, self._task_manager.setup_task.call_count - setup_call_count)
514
- self.assertEqual(1, self._task_manager.start.call_count - start_call_count)
515
- self._task_manager.update_task.assert_called_once_with(task)
516
- self.assertTrue(success)
517
- self._task_manager.stats.return_value = {}
518
- self.assertEqual(0, self._coordinator.stats()['failed_task_updates'])
519
-
520
- @mock.patch.object(time, 'sleep', autospec=True)
521
- def test_update_task_fails(self, unused_mock_sleep):
522
- task = task_pb2.Task(id='my_task')
523
- self._task_manager.setup_task.side_effect = errors.StepCommandError
524
- stop_call_count = self._task_manager.stop.call_count
525
- start_call_count = self._task_manager.start.call_count
526
- setup_call_count = self._task_manager.setup_task.call_count
527
- success = self._coordinator.update_task(task)
528
- self.assertEqual(1,
529
- self._task_manager.stop.call_count - stop_call_count)
530
- self.assertEqual(
531
- 1, self._task_manager.setup_task.call_count - setup_call_count)
532
- self.assertEqual( 1, self._task_manager.start.call_count - start_call_count)
533
- self._task_manager.update_task.assert_called_once_with(task)
534
- self.assertFalse(success)
535
- self._task_manager.stats.return_value = {}
536
- self.assertEqual(1, self._coordinator.stats()['failed_task_updates'])
537
-
538
281
 
539
282
  if __name__ == '__main__':
540
283
  absltest.main()
@@ -0,0 +1,174 @@
1
+ # coding=utf-8
2
+ # Copyright 2024 DeepMind Technologies Limited.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """Sets and gets some global settings on an Android device."""
17
+
18
+ from typing import Final
19
+ from unittest import mock
20
+
21
+ from absl import logging
22
+ from android_env.components import adb_call_parser
23
+ from android_env.components import config_classes
24
+ from android_env.components.simulators import base_simulator
25
+ from android_env.proto import adb_pb2
26
+ import numpy as np
27
+
28
+
29
+ # The internal `AdbCallParser` instance is lazily instantiated within
30
+ # `DeviceSettings`. If we make it optional (i.e. `| None`), pytype will think
31
+ # that it could be `None`, requiring either explicit runtime checks or escape
32
+ # hatches in every actual call, even if it's never actually `None` if reached
33
+ # via the public API.
34
+ # The trick here is to create this dummy instance of the right type that's used
35
+ # as a sentinel to indicate that it hasn't been initialized yet.
36
+ _PLACEHOLDER_ADB_CALL_PARSER: Final[adb_call_parser.AdbCallParser] = (
37
+ mock.create_autospec(adb_call_parser.AdbCallParser)
38
+ )
39
+
40
+
41
+ class DeviceSettings:
42
+ """An abstraction for general properties and settings of an Android device."""
43
+
44
+ def __init__(self, simulator: base_simulator.BaseSimulator):
45
+ self._simulator = simulator
46
+ self._adb_call_parser = _PLACEHOLDER_ADB_CALL_PARSER
47
+
48
+ # The size of the device screen in pixels.
49
+ self._screen_width: int = 0
50
+ self._screen_height: int = 0
51
+ # The device orientation.
52
+ self._orientation = np.zeros(4, dtype=np.uint8)
53
+
54
+ def update(self, config: config_classes.DeviceSettingsConfig) -> None:
55
+ """Sets the configuration of the device according to `config`."""
56
+
57
+ if self._adb_call_parser is _PLACEHOLDER_ADB_CALL_PARSER:
58
+ self._adb_call_parser = adb_call_parser.AdbCallParser(
59
+ adb_controller=self._simulator.create_adb_controller()
60
+ )
61
+
62
+ self._update_screen_size()
63
+ self._set_show_touches(config.show_touches)
64
+ self._set_show_pointer_location(config.show_pointer_location)
65
+ self._set_status_navigation_bars(
66
+ config.show_navigation_bar, config.show_status_bar
67
+ )
68
+
69
+ def screen_width(self) -> int:
70
+ """The screen width in pixels. Only valid after `update()` is called."""
71
+
72
+ return self._screen_width
73
+
74
+ def screen_height(self) -> int:
75
+ """The screen height in pixels. Only valid after `update()` is called."""
76
+
77
+ return self._screen_height
78
+
79
+ def get_orientation(self) -> np.ndarray:
80
+ """Returns the device orientation. Please see specs.py for details."""
81
+
82
+ if self._adb_call_parser is _PLACEHOLDER_ADB_CALL_PARSER:
83
+ self._adb_call_parser = adb_call_parser.AdbCallParser(
84
+ adb_controller=self._simulator.create_adb_controller()
85
+ )
86
+
87
+ self._update_orientation()
88
+ return self._orientation
89
+
90
+ def _update_screen_size(self) -> None:
91
+ """Sets the screen size from a screenshot ignoring the color channel."""
92
+
93
+ screenshot = self._simulator.get_screenshot()
94
+ self._screen_height = screenshot.shape[0]
95
+ self._screen_width = screenshot.shape[1]
96
+
97
+ def _set_show_touches(self, show: bool) -> None:
98
+ """Whether to display circles indicating the touch position."""
99
+
100
+ self._adb_call_parser.parse(
101
+ adb_pb2.AdbRequest(
102
+ settings=adb_pb2.AdbRequest.SettingsRequest(
103
+ name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.SYSTEM,
104
+ put=adb_pb2.AdbRequest.SettingsRequest.Put(
105
+ key='show_touches', value='1' if show else '0'
106
+ ),
107
+ )
108
+ )
109
+ )
110
+
111
+ def _set_show_pointer_location(self, show: bool) -> None:
112
+ """Whether to display blue lines on the screen indicating touch position."""
113
+
114
+ self._adb_call_parser.parse(
115
+ adb_pb2.AdbRequest(
116
+ settings=adb_pb2.AdbRequest.SettingsRequest(
117
+ name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.SYSTEM,
118
+ put=adb_pb2.AdbRequest.SettingsRequest.Put(
119
+ key='pointer_location', value='1' if show else '0'
120
+ ),
121
+ )
122
+ )
123
+ )
124
+
125
+ def _set_status_navigation_bars(
126
+ self, show_navigation: bool, show_status: bool
127
+ ) -> None:
128
+ """Whether to display the status (top) and navigation (bottom) bars."""
129
+
130
+ if show_navigation and show_status:
131
+ policy_control_value = 'null*'
132
+ elif show_navigation and not show_status:
133
+ policy_control_value = 'immersive.status=*'
134
+ elif not show_navigation and show_status:
135
+ policy_control_value = 'immersive.navigation=*'
136
+ else:
137
+ policy_control_value = 'immersive.full=*'
138
+
139
+ self._adb_call_parser.parse(
140
+ adb_pb2.AdbRequest(
141
+ settings=adb_pb2.AdbRequest.SettingsRequest(
142
+ name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.GLOBAL,
143
+ put=adb_pb2.AdbRequest.SettingsRequest.Put(
144
+ key='policy_control', value=policy_control_value
145
+ ),
146
+ )
147
+ )
148
+ )
149
+
150
+ def _update_orientation(self) -> None:
151
+ """Updates the current device orientation."""
152
+
153
+ # Skip fetching the orientation if we already have it.
154
+ if not np.all(self._orientation == np.zeros(4)):
155
+ return
156
+
157
+ orientation_response = self._adb_call_parser.parse(
158
+ adb_pb2.AdbRequest(
159
+ get_orientation=adb_pb2.AdbRequest.GetOrientationRequest()
160
+ )
161
+ )
162
+ if orientation_response.status != adb_pb2.AdbResponse.Status.OK:
163
+ logging.error('Got bad orientation: %r', orientation_response)
164
+ return
165
+
166
+ orientation = orientation_response.get_orientation.orientation
167
+ if orientation not in {0, 1, 2, 3}:
168
+ logging.error('Got bad orientation: %r', orientation)
169
+ return
170
+
171
+ # Transform into one-hot format.
172
+ orientation_onehot = np.zeros([4], dtype=np.uint8)
173
+ orientation_onehot[orientation] = 1
174
+ self._orientation = orientation_onehot