android-env 1.2.1__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 (145) 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 +239 -196
  14. android_env/components/adb_call_parser_test.py +179 -209
  15. android_env/components/adb_controller.py +90 -52
  16. android_env/components/adb_controller_test.py +187 -16
  17. android_env/components/adb_log_stream.py +17 -5
  18. android_env/components/adb_log_stream_test.py +17 -3
  19. android_env/components/app_screen_checker.py +17 -15
  20. android_env/components/app_screen_checker_test.py +7 -8
  21. android_env/components/config_classes.py +203 -0
  22. android_env/components/coordinator.py +102 -338
  23. android_env/components/coordinator_test.py +59 -199
  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 +52 -10
  29. android_env/components/errors_test.py +110 -0
  30. android_env/components/log_stream.py +7 -5
  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 +3 -4
  34. android_env/components/{utils.py → pixel_fns.py} +20 -20
  35. android_env/components/{utils_test.py → pixel_fns_test.py} +20 -15
  36. android_env/components/setup_step_interpreter.py +47 -39
  37. android_env/components/setup_step_interpreter_test.py +4 -4
  38. android_env/components/simulators/__init__.py +1 -1
  39. android_env/components/simulators/base_simulator.py +116 -44
  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 +67 -77
  43. android_env/components/simulators/emulator/emulator_launcher_test.py +153 -49
  44. android_env/components/simulators/emulator/emulator_simulator.py +276 -95
  45. android_env/components/simulators/emulator/emulator_simulator_test.py +314 -89
  46. android_env/components/simulators/fake/__init__.py +1 -1
  47. android_env/components/simulators/fake/fake_simulator.py +17 -25
  48. android_env/components/simulators/fake/fake_simulator_test.py +29 -12
  49. android_env/components/specs.py +18 -28
  50. android_env/components/specs_test.py +1 -44
  51. android_env/components/task_manager.py +48 -48
  52. android_env/components/task_manager_test.py +71 -60
  53. android_env/env_interface.py +37 -23
  54. android_env/environment.py +83 -51
  55. android_env/environment_test.py +68 -29
  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 +17 -6
  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 +68 -63
  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 +505 -142
  100. android_env/proto/snapshot.proto +169 -0
  101. android_env/proto/snapshot_pb2.py +47 -0
  102. android_env/proto/snapshot_pb2.pyi +117 -0
  103. android_env/proto/snapshot_pb2_grpc.py +24 -0
  104. android_env/proto/snapshot_service.proto +289 -0
  105. android_env/proto/snapshot_service_pb2.py +54 -0
  106. android_env/proto/snapshot_service_pb2.pyi +86 -0
  107. android_env/proto/snapshot_service_pb2_grpc.py +487 -0
  108. android_env/proto/state.proto +63 -0
  109. android_env/proto/state_pb2.py +63 -0
  110. android_env/proto/state_pb2.pyi +85 -0
  111. android_env/proto/state_pb2_grpc.py +24 -0
  112. android_env/proto/task.proto +5 -1
  113. android_env/proto/task_pb2.py +42 -31
  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 +34 -13
  120. android_env/wrappers/base_wrapper_test.py +22 -16
  121. android_env/wrappers/discrete_action_wrapper.py +18 -17
  122. android_env/wrappers/discrete_action_wrapper_test.py +4 -4
  123. android_env/wrappers/flat_interface_wrapper.py +5 -5
  124. android_env/wrappers/flat_interface_wrapper_test.py +7 -11
  125. android_env/wrappers/float_pixels_wrapper.py +9 -10
  126. android_env/wrappers/float_pixels_wrapper_test.py +3 -3
  127. android_env/wrappers/gym_wrapper.py +19 -13
  128. android_env/wrappers/gym_wrapper_test.py +3 -5
  129. android_env/wrappers/image_rescale_wrapper.py +18 -21
  130. android_env/wrappers/image_rescale_wrapper_test.py +25 -37
  131. android_env/wrappers/last_action_wrapper.py +16 -13
  132. android_env/wrappers/last_action_wrapper_test.py +44 -51
  133. android_env/wrappers/rate_limit_wrapper.py +6 -3
  134. android_env/wrappers/rate_limit_wrapper_test.py +22 -1
  135. android_env/wrappers/tap_action_wrapper.py +16 -17
  136. android_env/wrappers/tap_action_wrapper_test.py +51 -16
  137. {android_env-1.2.1.dist-info → android_env-1.2.3.dist-info}/METADATA +14 -18
  138. android_env-1.2.3.dist-info/RECORD +141 -0
  139. {android_env-1.2.1.dist-info → android_env-1.2.3.dist-info}/WHEEL +1 -1
  140. android_env/proto/raw_observation.proto +0 -39
  141. android_env/proto/raw_observation_pb2.py +0 -27
  142. android_env/proto/raw_observation_pb2_grpc.py +0 -4
  143. android_env-1.2.1.dist-info/RECORD +0 -81
  144. {android_env-1.2.1.dist-info → android_env-1.2.3.dist-info/licenses}/LICENSE +0 -0
  145. {android_env-1.2.1.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 2022 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,11 +23,14 @@ 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
30
32
  from android_env.proto import adb_pb2
33
+ from android_env.proto import state_pb2
31
34
  from android_env.proto import task_pb2
32
35
  import dm_env
33
36
  import numpy as np
@@ -51,30 +54,50 @@ class CoordinatorTest(parameterized.TestCase):
51
54
  'AdbCallParser',
52
55
  autospec=True,
53
56
  return_value=self._adb_call_parser))
54
- self.enter_context(mock.patch.object(time, 'sleep', autospec=True))
55
57
  self._coordinator = coordinator_lib.Coordinator(
56
58
  simulator=self._simulator,
57
59
  task_manager=self._task_manager,
58
- num_fingers=1,
59
- periodic_restart_time_min=0,
60
- check_services_max_tries=0)
60
+ device_settings=device_settings_lib.DeviceSettings(self._simulator),
61
+ )
61
62
 
62
- def test_restart_simulator(self):
63
- restart_count = self._coordinator.stats()['restart_count']
64
- self._coordinator._restart_simulator()
65
- self.assertEqual(self._coordinator.stats()['restart_count'],
66
- restart_count + 1)
63
+ def tearDown(self):
64
+ super().tearDown()
65
+ self._coordinator.close()
67
66
 
68
- def test_reset(self):
67
+ @mock.patch.object(time, 'sleep', autospec=True)
68
+ def test_relaunch_simulator(self, unused_mock_sleep):
69
+ relaunch_count = self._coordinator.stats()['relaunch_count']
70
+ self._coordinator._launch_simulator()
71
+ self.assertEqual(self._coordinator.stats()['relaunch_count'],
72
+ relaunch_count + 1)
73
+
74
+ @mock.patch.object(time, 'sleep', autospec=True)
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()
69
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)
70
92
 
71
- def test_lift_all_fingers(self):
93
+ @mock.patch.object(time, 'sleep', autospec=True)
94
+ def test_lift_all_fingers(self, unused_mock_sleep):
72
95
  self._coordinator = coordinator_lib.Coordinator(
73
96
  simulator=self._simulator,
74
97
  task_manager=self._task_manager,
75
- num_fingers=3,
76
- periodic_restart_time_min=0,
77
- check_services_max_tries=0)
98
+ device_settings=device_settings_lib.DeviceSettings(self._simulator),
99
+ config=config_classes.CoordinatorConfig(num_fingers=3),
100
+ )
78
101
  self._coordinator.rl_reset()
79
102
  expected_actions = [
80
103
  # (x, y, is_down, identifier).
@@ -86,7 +109,8 @@ class CoordinatorTest(parameterized.TestCase):
86
109
  for actual, expected in zip(actual_actions, expected_actions):
87
110
  np.testing.assert_array_equal(actual, expected)
88
111
 
89
- def test_process_action(self):
112
+ @mock.patch.object(time, 'sleep', autospec=True)
113
+ def test_process_action(self, unused_mock_sleep):
90
114
 
91
115
  def fake_rl_step(simulator_signals):
92
116
  return dm_env.transition(
@@ -114,7 +138,8 @@ class CoordinatorTest(parameterized.TestCase):
114
138
  self.assertEqual(obs['extras'], {'extra': [0.0]})
115
139
  self.assertFalse(timestep.last())
116
140
 
117
- def test_process_action_error(self):
141
+ @mock.patch.object(time, 'sleep', autospec=True)
142
+ def test_process_action_error(self, unused_mock_sleep):
118
143
 
119
144
  def fake_rl_step(simulator_signals):
120
145
  self.assertFalse(simulator_signals['simulator_healthy'])
@@ -131,7 +156,8 @@ class CoordinatorTest(parameterized.TestCase):
131
156
  self.assertEqual(timestep.reward, 0.0)
132
157
  self.assertTrue(timestep.last())
133
158
 
134
- def test_execute_action_touch(self):
159
+ @mock.patch.object(time, 'sleep', autospec=True)
160
+ def test_execute_action_touch(self, unused_mock_sleep):
135
161
 
136
162
  def fake_rl_step(simulator_signals):
137
163
  return dm_env.transition(
@@ -156,13 +182,14 @@ class CoordinatorTest(parameterized.TestCase):
156
182
  self._random_screenshot)
157
183
  self._simulator.send_touch.assert_called_once_with([(300, 400, True, 0)])
158
184
 
159
- def test_execute_multitouch_action(self):
185
+ @mock.patch.object(time, 'sleep', autospec=True)
186
+ def test_execute_multitouch_action(self, unused_mock_sleep):
160
187
  self._coordinator = coordinator_lib.Coordinator(
161
188
  simulator=self._simulator,
162
189
  task_manager=self._task_manager,
163
- num_fingers=3,
164
- periodic_restart_time_min=0,
165
- check_services_max_tries=0)
190
+ device_settings=device_settings_lib.DeviceSettings(self._simulator),
191
+ config=config_classes.CoordinatorConfig(num_fingers=3),
192
+ )
166
193
 
167
194
  def fake_rl_step(simulator_signals):
168
195
  return dm_env.transition(
@@ -193,8 +220,8 @@ class CoordinatorTest(parameterized.TestCase):
193
220
  np.testing.assert_equal(timestep.observation['pixels'],
194
221
  self._random_screenshot)
195
222
 
196
- def test_execute_action_repeat(self):
197
-
223
+ @mock.patch.object(time, 'sleep', autospec=True)
224
+ def test_execute_action_repeat(self, unused_mock_sleep):
198
225
  def fake_rl_step(simulator_signals):
199
226
  return dm_env.transition(
200
227
  reward=10.0,
@@ -214,8 +241,8 @@ class CoordinatorTest(parameterized.TestCase):
214
241
  np.testing.assert_equal(timestep.observation['pixels'],
215
242
  self._random_screenshot)
216
243
 
217
- def test_execute_action_error(self):
218
-
244
+ @mock.patch.object(time, 'sleep', autospec=True)
245
+ def test_execute_action_error(self, unused_mock_sleep):
219
246
  def fake_rl_step(simulator_signals):
220
247
  self.assertFalse(simulator_signals['simulator_healthy'])
221
248
  return dm_env.truncation(reward=0.0, observation=None)
@@ -228,16 +255,18 @@ class CoordinatorTest(parameterized.TestCase):
228
255
  })
229
256
  self.assertIsNone(timestep.observation)
230
257
 
231
- def test_max_restarts_setup_steps(self):
258
+ @mock.patch.object(time, 'sleep', autospec=True)
259
+ def test_max_restarts_setup_steps(self, unused_mock_sleep):
232
260
  init_fn_call = self._task_manager.setup_task.call_count
233
261
  self._task_manager.setup_task.side_effect = errors.StepCommandError
234
262
  self.assertRaises(errors.TooManyRestartsError,
235
- self._coordinator._restart_simulator)
236
- # The method was called three more times when attempting to restart.
263
+ self._coordinator._launch_simulator)
264
+ # The method was called three more times when attempting to relaunch.
237
265
  self.assertEqual(init_fn_call + 3,
238
266
  self._task_manager.setup_task.call_count)
239
267
 
240
- def test_execute_adb_call(self):
268
+ @mock.patch.object(time, 'sleep', autospec=True)
269
+ def test_execute_adb_call(self, unused_mock_sleep):
241
270
  call = adb_pb2.AdbRequest(
242
271
  force_stop=adb_pb2.AdbRequest.ForceStop(package_name='blah'))
243
272
  expected_response = adb_pb2.AdbResponse(
@@ -249,175 +278,6 @@ class CoordinatorTest(parameterized.TestCase):
249
278
  self.assertEqual(response, expected_response)
250
279
  self._adb_call_parser.parse.assert_called_with(call)
251
280
 
252
- @mock.patch.object(tempfile, 'gettempdir', autospec=True)
253
- def test_with_tmp_dir_no_tempfile_call(self, mock_gettempdir):
254
- """If passing a `tmp_dir`, `tempfile.gettempdir()` should not be called."""
255
- _ = coordinator_lib.Coordinator(
256
- simulator=self._simulator,
257
- task_manager=self._task_manager,
258
- periodic_restart_time_min=0,
259
- tmp_dir=absltest.get_default_test_tmpdir(),
260
- check_services_max_tries=0)
261
- mock_gettempdir.assert_not_called()
262
-
263
- @mock.patch.object(tempfile, 'gettempdir', autospec=True)
264
- def test_no_tmp_dir_calls_tempfile(self, mock_gettempdir):
265
- """If not passing a `tmp_dir`, `tempfile.gettempdir()` should be called."""
266
- _ = coordinator_lib.Coordinator(
267
- simulator=self._simulator,
268
- task_manager=self._task_manager,
269
- periodic_restart_time_min=0,
270
- check_services_max_tries=0)
271
- mock_gettempdir.assert_called_once()
272
-
273
- @parameterized.parameters(
274
- (True, '1'),
275
- (False, '0'),
276
- )
277
- def test_touch_indicator(self, show, expected_value):
278
- _ = coordinator_lib.Coordinator(
279
- simulator=self._simulator,
280
- task_manager=self._task_manager,
281
- check_services_max_tries=0,
282
- show_touches=show)
283
- self._adb_call_parser.parse.assert_any_call(
284
- adb_pb2.AdbRequest(
285
- settings=adb_pb2.AdbRequest.SettingsRequest(
286
- name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.SYSTEM,
287
- put=adb_pb2.AdbRequest.SettingsRequest.Put(
288
- key='show_touches', value=expected_value))))
289
-
290
- @parameterized.parameters(
291
- (True, '1'),
292
- (False, '0'),
293
- )
294
- def test_pointer_location(self, show, expected_value):
295
- _ = coordinator_lib.Coordinator(
296
- simulator=self._simulator,
297
- task_manager=self._task_manager,
298
- check_services_max_tries=0,
299
- show_pointer_location=show)
300
- self._adb_call_parser.parse.assert_any_call(
301
- adb_pb2.AdbRequest(
302
- settings=adb_pb2.AdbRequest.SettingsRequest(
303
- name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.SYSTEM,
304
- put=adb_pb2.AdbRequest.SettingsRequest.Put(
305
- key='pointer_location', value=expected_value))))
306
-
307
- @parameterized.parameters(
308
- (True, True, 'null*'),
309
- (True, False, 'immersive.status=*'),
310
- (False, True, 'immersive.navigation=*'),
311
- (False, False, 'immersive.full=*'),
312
- (None, None, 'immersive.full=*'), # Defaults to hiding both.
313
- )
314
- def test_bar_visibility(self, show_navigation_bar, show_status_bar,
315
- expected_value):
316
- _ = coordinator_lib.Coordinator(
317
- simulator=self._simulator,
318
- task_manager=self._task_manager,
319
- check_services_max_tries=0,
320
- show_navigation_bar=show_navigation_bar,
321
- show_status_bar=show_status_bar)
322
- self._adb_call_parser.parse.assert_any_call(
323
- adb_pb2.AdbRequest(
324
- settings=adb_pb2.AdbRequest.SettingsRequest(
325
- name_space=adb_pb2.AdbRequest.SettingsRequest.Namespace.GLOBAL,
326
- put=adb_pb2.AdbRequest.SettingsRequest.Put(
327
- key='policy_control', value=expected_value))))
328
-
329
- def test_wait_for_device(self):
330
- """Ensures that all required services are up and running."""
331
-
332
- def _parse_fn(adb_call: adb_pb2.AdbRequest):
333
- _parse_fn.call_count += 1
334
- if adb_call.generic.args:
335
- # Initially, return "not found".
336
- if _parse_fn.call_count < 3:
337
- return adb_pb2.AdbResponse(
338
- generic=adb_pb2.AdbResponse.GenericResponse(
339
- output=b'Service window: NOT found'))
340
-
341
- # Return "found" after 2 calls.
342
- service = adb_call.generic.args[3]
343
- return adb_pb2.AdbResponse(
344
- status=adb_pb2.AdbResponse.Status.OK,
345
- generic=adb_pb2.AdbResponse.GenericResponse(
346
- output=f'Service {service}: found'.encode('utf-8')))
347
-
348
- _parse_fn.call_count = 0
349
-
350
- self._adb_call_parser.parse.side_effect = _parse_fn
351
-
352
- _ = coordinator_lib.Coordinator(
353
- simulator=self._simulator, task_manager=self._task_manager)
354
- self._adb_call_parser.parse.assert_has_calls([
355
- mock.call(
356
- adb_pb2.AdbRequest(
357
- generic=adb_pb2.AdbRequest.GenericRequest(
358
- args=['shell', 'service', 'check', 'window']))),
359
- mock.call(
360
- adb_pb2.AdbRequest(
361
- generic=adb_pb2.AdbRequest.GenericRequest(
362
- args=['shell', 'service', 'check', 'package']))),
363
- mock.call(
364
- adb_pb2.AdbRequest(
365
- generic=adb_pb2.AdbRequest.GenericRequest(
366
- args=['shell', 'service', 'check', 'input']))),
367
- mock.call(
368
- adb_pb2.AdbRequest(
369
- generic=adb_pb2.AdbRequest.GenericRequest(
370
- args=['shell', 'service', 'check', 'display']))),
371
- ],
372
- any_order=True)
373
-
374
- def test_wait_for_device_exceeded_max_tries(self):
375
- """If the required services are not found, it should raise an exception."""
376
-
377
- def _parse_fn(adb_call: adb_pb2.AdbRequest):
378
- if adb_call.generic.args:
379
- # Return "not found" repeatedly.
380
- return adb_pb2.AdbResponse(
381
- generic=adb_pb2.AdbResponse.GenericResponse(
382
- output=b'Service window: NOT found'))
383
-
384
- self._adb_call_parser.parse.side_effect = _parse_fn
385
-
386
- self.assertRaises(
387
- errors.AdbControllerDeviceTimeoutError,
388
- coordinator_lib.Coordinator,
389
- simulator=self._simulator,
390
- task_manager=self._task_manager)
391
-
392
- def test_update_task_succeeds(self):
393
- task = task_pb2.Task(id='my_task')
394
- stop_call_count = self._task_manager.stop_task.call_count
395
- setup_call_count = self._task_manager.setup_task.call_count
396
- success = self._coordinator.update_task(task)
397
- self.assertEqual(1,
398
- self._task_manager.stop_task.call_count - stop_call_count)
399
- self.assertEqual(
400
- 1, self._task_manager.setup_task.call_count - setup_call_count)
401
- self._task_manager.update_task.assert_called_once_with(task)
402
- self.assertTrue(success)
403
- self._task_manager.stats.return_value = {}
404
- self.assertEqual(0, self._coordinator.stats()['failed_task_updates'])
405
-
406
- def test_update_task_fails(self):
407
- task = task_pb2.Task(id='my_task')
408
- self._task_manager.setup_task.side_effect = errors.StepCommandError
409
- stop_call_count = self._task_manager.stop_task.call_count
410
- setup_call_count = self._task_manager.setup_task.call_count
411
- success = self._coordinator.update_task(task)
412
- self.assertEqual(1,
413
- self._task_manager.stop_task.call_count - stop_call_count)
414
- self.assertEqual(
415
- 1, self._task_manager.setup_task.call_count - setup_call_count)
416
- self._task_manager.update_task.assert_called_once_with(task)
417
- self.assertFalse(success)
418
- self._task_manager.stats.return_value = {}
419
- self.assertEqual(1, self._coordinator.stats()['failed_task_updates'])
420
-
421
281
 
422
282
  if __name__ == '__main__':
423
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