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.
- 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 +239 -196
- android_env/components/adb_call_parser_test.py +179 -209
- android_env/components/adb_controller.py +90 -52
- android_env/components/adb_controller_test.py +187 -16
- android_env/components/adb_log_stream.py +17 -5
- android_env/components/adb_log_stream_test.py +17 -3
- android_env/components/app_screen_checker.py +17 -15
- android_env/components/app_screen_checker_test.py +7 -8
- android_env/components/config_classes.py +203 -0
- android_env/components/coordinator.py +102 -338
- android_env/components/coordinator_test.py +59 -199
- 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 +52 -10
- android_env/components/errors_test.py +110 -0
- android_env/components/log_stream.py +7 -5
- android_env/components/log_stream_test.py +1 -1
- android_env/components/logcat_thread.py +9 -8
- android_env/components/logcat_thread_test.py +3 -4
- android_env/components/{utils.py → pixel_fns.py} +20 -20
- android_env/components/{utils_test.py → pixel_fns_test.py} +20 -15
- android_env/components/setup_step_interpreter.py +47 -39
- android_env/components/setup_step_interpreter_test.py +4 -4
- android_env/components/simulators/__init__.py +1 -1
- android_env/components/simulators/base_simulator.py +116 -44
- 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 +67 -77
- android_env/components/simulators/emulator/emulator_launcher_test.py +153 -49
- android_env/components/simulators/emulator/emulator_simulator.py +276 -95
- android_env/components/simulators/emulator/emulator_simulator_test.py +314 -89
- android_env/components/simulators/fake/__init__.py +1 -1
- android_env/components/simulators/fake/fake_simulator.py +17 -25
- android_env/components/simulators/fake/fake_simulator_test.py +29 -12
- android_env/components/specs.py +18 -28
- android_env/components/specs_test.py +1 -44
- android_env/components/task_manager.py +48 -48
- android_env/components/task_manager_test.py +71 -60
- android_env/env_interface.py +37 -23
- android_env/environment.py +83 -51
- android_env/environment_test.py +68 -29
- 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 +17 -6
- 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 +68 -63
- 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 +505 -142
- android_env/proto/snapshot.proto +169 -0
- android_env/proto/snapshot_pb2.py +47 -0
- android_env/proto/snapshot_pb2.pyi +117 -0
- android_env/proto/snapshot_pb2_grpc.py +24 -0
- android_env/proto/snapshot_service.proto +289 -0
- android_env/proto/snapshot_service_pb2.py +54 -0
- android_env/proto/snapshot_service_pb2.pyi +86 -0
- android_env/proto/snapshot_service_pb2_grpc.py +487 -0
- android_env/proto/state.proto +63 -0
- android_env/proto/state_pb2.py +63 -0
- android_env/proto/state_pb2.pyi +85 -0
- android_env/proto/state_pb2_grpc.py +24 -0
- android_env/proto/task.proto +5 -1
- android_env/proto/task_pb2.py +42 -31
- 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 +34 -13
- android_env/wrappers/base_wrapper_test.py +22 -16
- android_env/wrappers/discrete_action_wrapper.py +18 -17
- android_env/wrappers/discrete_action_wrapper_test.py +4 -4
- android_env/wrappers/flat_interface_wrapper.py +5 -5
- android_env/wrappers/flat_interface_wrapper_test.py +7 -11
- android_env/wrappers/float_pixels_wrapper.py +9 -10
- android_env/wrappers/float_pixels_wrapper_test.py +3 -3
- android_env/wrappers/gym_wrapper.py +19 -13
- android_env/wrappers/gym_wrapper_test.py +3 -5
- android_env/wrappers/image_rescale_wrapper.py +18 -21
- android_env/wrappers/image_rescale_wrapper_test.py +25 -37
- android_env/wrappers/last_action_wrapper.py +16 -13
- android_env/wrappers/last_action_wrapper_test.py +44 -51
- android_env/wrappers/rate_limit_wrapper.py +6 -3
- android_env/wrappers/rate_limit_wrapper_test.py +22 -1
- android_env/wrappers/tap_action_wrapper.py +16 -17
- android_env/wrappers/tap_action_wrapper_test.py +51 -16
- {android_env-1.2.1.dist-info → android_env-1.2.3.dist-info}/METADATA +14 -18
- android_env-1.2.3.dist-info/RECORD +141 -0
- {android_env-1.2.1.dist-info → android_env-1.2.3.dist-info}/WHEEL +1 -1
- android_env/proto/raw_observation.proto +0 -39
- android_env/proto/raw_observation_pb2.py +0 -27
- android_env/proto/raw_observation_pb2_grpc.py +0 -4
- android_env-1.2.1.dist-info/RECORD +0 -81
- {android_env-1.2.1.dist-info → android_env-1.2.3.dist-info/licenses}/LICENSE +0 -0
- {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
|
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
|
-
|
59
|
-
|
60
|
-
check_services_max_tries=0)
|
60
|
+
device_settings=device_settings_lib.DeviceSettings(self._simulator),
|
61
|
+
)
|
61
62
|
|
62
|
-
def
|
63
|
-
|
64
|
-
self._coordinator.
|
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
|
-
|
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
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
164
|
-
|
165
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
236
|
-
# The method was called three more times when attempting to
|
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
|
-
|
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
|