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.
- android_env/__init__.py +1 -1
- android_env/components/__init__.py +1 -1
- android_env/components/a11y/__init__.py +15 -0
- android_env/components/a11y/a11y_events.py +118 -0
- android_env/components/a11y/a11y_events_test.py +173 -0
- android_env/components/a11y/a11y_forests.py +128 -0
- android_env/components/a11y/a11y_forests_test.py +237 -0
- android_env/components/a11y/a11y_servicer.py +199 -0
- android_env/components/a11y/a11y_servicer_test.py +224 -0
- android_env/components/action_fns.py +132 -0
- android_env/components/action_fns_test.py +227 -0
- android_env/components/action_type.py +26 -3
- android_env/components/adb_call_parser.py +233 -185
- android_env/components/adb_call_parser_test.py +165 -163
- android_env/components/adb_controller.py +19 -28
- android_env/components/adb_controller_test.py +100 -9
- android_env/components/adb_log_stream.py +3 -3
- android_env/components/adb_log_stream_test.py +1 -1
- android_env/components/app_screen_checker.py +15 -13
- android_env/components/app_screen_checker_test.py +1 -1
- android_env/components/config_classes.py +203 -0
- android_env/components/coordinator.py +53 -338
- android_env/components/coordinator_test.py +26 -283
- android_env/components/device_settings.py +174 -0
- android_env/components/device_settings_test.py +228 -0
- android_env/components/dumpsys_thread.py +3 -4
- android_env/components/dumpsys_thread_test.py +1 -1
- android_env/components/errors.py +2 -5
- android_env/components/errors_test.py +1 -1
- android_env/components/log_stream.py +2 -2
- android_env/components/log_stream_test.py +1 -1
- android_env/components/logcat_thread.py +9 -8
- android_env/components/logcat_thread_test.py +2 -3
- android_env/components/{utils.py → pixel_fns.py} +19 -20
- android_env/components/{utils_test.py → pixel_fns_test.py} +20 -15
- android_env/components/setup_step_interpreter.py +45 -37
- android_env/components/setup_step_interpreter_test.py +1 -1
- android_env/components/simulators/__init__.py +1 -1
- android_env/components/simulators/base_simulator.py +79 -23
- android_env/components/simulators/base_simulator_test.py +131 -9
- android_env/components/simulators/emulator/__init__.py +1 -1
- android_env/components/simulators/emulator/emulator_launcher.py +62 -81
- android_env/components/simulators/emulator/emulator_launcher_test.py +120 -43
- android_env/components/simulators/emulator/emulator_simulator.py +111 -98
- android_env/components/simulators/emulator/emulator_simulator_test.py +174 -138
- android_env/components/simulators/fake/__init__.py +1 -1
- android_env/components/simulators/fake/fake_simulator.py +9 -17
- android_env/components/simulators/fake/fake_simulator_test.py +23 -8
- android_env/components/specs.py +1 -1
- android_env/components/specs_test.py +1 -1
- android_env/components/task_manager.py +26 -31
- android_env/components/task_manager_test.py +1 -18
- android_env/env_interface.py +1 -17
- android_env/environment.py +27 -17
- android_env/environment_test.py +51 -25
- android_env/loader.py +57 -43
- android_env/loader_test.py +115 -35
- android_env/proto/__init__.py +1 -1
- android_env/proto/a11y/__init__.py +15 -0
- android_env/proto/a11y/a11y.proto +75 -0
- android_env/proto/a11y/a11y_pb2.py +54 -0
- android_env/proto/a11y/a11y_pb2.pyi +49 -0
- android_env/proto/a11y/a11y_pb2_grpc.py +202 -0
- android_env/proto/a11y/android_accessibility_action.proto +32 -0
- android_env/proto/a11y/android_accessibility_action_pb2.py +37 -0
- android_env/proto/a11y/android_accessibility_action_pb2.pyi +13 -0
- android_env/proto/a11y/android_accessibility_action_pb2_grpc.py +24 -0
- android_env/proto/a11y/android_accessibility_forest.proto +29 -0
- android_env/proto/a11y/android_accessibility_forest_pb2.py +38 -0
- android_env/proto/a11y/android_accessibility_forest_pb2.pyi +13 -0
- android_env/proto/a11y/android_accessibility_forest_pb2_grpc.py +24 -0
- android_env/proto/a11y/android_accessibility_node_info.proto +122 -0
- android_env/proto/a11y/android_accessibility_node_info_clickable_span.proto +49 -0
- android_env/proto/a11y/android_accessibility_node_info_clickable_span_pb2.py +39 -0
- android_env/proto/a11y/android_accessibility_node_info_clickable_span_pb2.pyi +28 -0
- android_env/proto/a11y/android_accessibility_node_info_clickable_span_pb2_grpc.py +24 -0
- android_env/proto/a11y/android_accessibility_node_info_pb2.py +42 -0
- android_env/proto/a11y/android_accessibility_node_info_pb2.pyi +75 -0
- android_env/proto/a11y/android_accessibility_node_info_pb2_grpc.py +24 -0
- android_env/proto/a11y/android_accessibility_tree.proto +29 -0
- android_env/proto/a11y/android_accessibility_tree_pb2.py +38 -0
- android_env/proto/a11y/android_accessibility_tree_pb2.pyi +13 -0
- android_env/proto/a11y/android_accessibility_tree_pb2_grpc.py +24 -0
- android_env/proto/a11y/android_accessibility_window_info.proto +84 -0
- android_env/proto/a11y/android_accessibility_window_info_pb2.py +41 -0
- android_env/proto/a11y/android_accessibility_window_info_pb2.pyi +48 -0
- android_env/proto/a11y/android_accessibility_window_info_pb2_grpc.py +24 -0
- android_env/proto/a11y/rect.proto +30 -0
- android_env/proto/a11y/rect_pb2.py +37 -0
- android_env/proto/a11y/rect_pb2.pyi +17 -0
- android_env/proto/a11y/rect_pb2_grpc.py +24 -0
- android_env/proto/adb.proto +13 -1
- android_env/proto/adb_pb2.py +120 -107
- android_env/proto/adb_pb2.pyi +396 -0
- android_env/proto/adb_pb2_grpc.py +20 -0
- android_env/proto/emulator_controller.proto +1 -1
- android_env/proto/emulator_controller_pb2.py +142 -131
- android_env/proto/emulator_controller_pb2.pyi +672 -0
- android_env/proto/emulator_controller_pb2_grpc.py +497 -136
- android_env/proto/snapshot.proto +1 -1
- android_env/proto/snapshot_pb2.py +30 -19
- android_env/proto/snapshot_pb2.pyi +117 -0
- android_env/proto/snapshot_pb2_grpc.py +20 -0
- android_env/proto/snapshot_service.proto +1 -1
- android_env/proto/snapshot_service_pb2.py +36 -25
- android_env/proto/snapshot_service_pb2.pyi +86 -0
- android_env/proto/snapshot_service_pb2_grpc.py +119 -28
- android_env/proto/state.proto +1 -1
- android_env/proto/state_pb2.py +46 -35
- android_env/proto/state_pb2.pyi +85 -0
- android_env/proto/state_pb2_grpc.py +20 -0
- android_env/proto/task.proto +4 -1
- android_env/proto/task_pb2.py +41 -30
- android_env/proto/task_pb2.pyi +160 -0
- android_env/proto/task_pb2_grpc.py +20 -0
- android_env/wrappers/__init__.py +1 -1
- android_env/wrappers/a11y_grpc_wrapper.py +500 -0
- android_env/wrappers/a11y_grpc_wrapper_test.py +849 -0
- android_env/wrappers/base_wrapper.py +1 -5
- android_env/wrappers/base_wrapper_test.py +1 -7
- android_env/wrappers/discrete_action_wrapper.py +15 -14
- android_env/wrappers/discrete_action_wrapper_test.py +1 -1
- android_env/wrappers/flat_interface_wrapper.py +5 -5
- android_env/wrappers/flat_interface_wrapper_test.py +1 -1
- android_env/wrappers/float_pixels_wrapper.py +5 -4
- android_env/wrappers/float_pixels_wrapper_test.py +1 -1
- android_env/wrappers/gym_wrapper.py +1 -1
- android_env/wrappers/gym_wrapper_test.py +1 -1
- android_env/wrappers/image_rescale_wrapper.py +13 -10
- android_env/wrappers/image_rescale_wrapper_test.py +1 -1
- android_env/wrappers/last_action_wrapper.py +5 -4
- android_env/wrappers/last_action_wrapper_test.py +1 -1
- android_env/wrappers/rate_limit_wrapper.py +1 -1
- android_env/wrappers/rate_limit_wrapper_test.py +1 -1
- android_env/wrappers/tap_action_wrapper.py +12 -12
- android_env/wrappers/tap_action_wrapper_test.py +49 -14
- {android_env-1.2.2.dist-info → android_env-1.2.3.dist-info}/METADATA +14 -16
- android_env-1.2.3.dist-info/RECORD +141 -0
- {android_env-1.2.2.dist-info → android_env-1.2.3.dist-info}/WHEEL +1 -1
- android_env-1.2.2.dist-info/RECORD +0 -88
- {android_env-1.2.2.dist-info → android_env-1.2.3.dist-info/licenses}/LICENSE +0 -0
- {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
|
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
|
-
|
68
|
-
|
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
|
-
|
91
|
-
|
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
|
-
|
312
|
-
|
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
|