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.
|
@@ -16,34 +16,23 @@
|
|
16
16
|
"""A base class for talking to different types of Android simulators."""
|
17
17
|
|
18
18
|
import abc
|
19
|
+
from collections.abc import Callable
|
20
|
+
import threading
|
21
|
+
import time
|
19
22
|
|
20
23
|
from absl import logging
|
21
24
|
from android_env.components import adb_controller
|
25
|
+
from android_env.components import config_classes
|
22
26
|
from android_env.components import errors
|
23
27
|
from android_env.components import log_stream
|
24
28
|
from android_env.proto import state_pb2
|
25
29
|
import numpy as np
|
26
30
|
|
27
31
|
|
28
|
-
def _print_logs_on_exception(func):
|
29
|
-
"""Decorator function for printing simulator logs upon any exception."""
|
30
|
-
def wrapper(*args, **kwargs):
|
31
|
-
try:
|
32
|
-
return func(*args, **kwargs)
|
33
|
-
except Exception as error:
|
34
|
-
# Calls self.get_logs since self is the first arg.
|
35
|
-
for line in args[0].get_logs().splitlines():
|
36
|
-
logging.error(line)
|
37
|
-
raise errors.SimulatorError(
|
38
|
-
'Exception caught in simulator. Please see the simulator logs '
|
39
|
-
'above for more details.') from error
|
40
|
-
return wrapper
|
41
|
-
|
42
|
-
|
43
32
|
class BaseSimulator(metaclass=abc.ABCMeta):
|
44
33
|
"""An interface for communicating with an Android simulator."""
|
45
34
|
|
46
|
-
def __init__(self,
|
35
|
+
def __init__(self, config: config_classes.SimulatorConfig):
|
47
36
|
"""Instantiates a BaseSimulator object.
|
48
37
|
|
49
38
|
The simulator may be an emulator, virtual machine or even a physical device.
|
@@ -51,10 +40,11 @@ class BaseSimulator(metaclass=abc.ABCMeta):
|
|
51
40
|
bookkeeping.
|
52
41
|
|
53
42
|
Args:
|
54
|
-
|
43
|
+
config: Settings for this simulator.
|
55
44
|
"""
|
56
45
|
|
57
|
-
self.
|
46
|
+
self._config = config
|
47
|
+
self._interaction_thread: InteractionThread | None = None
|
58
48
|
|
59
49
|
# An increasing number that tracks the attempt at launching the simulator.
|
60
50
|
self._num_launch_attempts: int = 0
|
@@ -75,12 +65,31 @@ class BaseSimulator(metaclass=abc.ABCMeta):
|
|
75
65
|
def create_log_stream(self) -> log_stream.LogStream:
|
76
66
|
"""Creates a stream of logs from the simulator."""
|
77
67
|
|
78
|
-
@_print_logs_on_exception
|
79
68
|
def launch(self) -> None:
|
80
69
|
"""Starts the simulator."""
|
81
70
|
|
71
|
+
# Stop screenshot thread if it's enabled.
|
72
|
+
if self._interaction_thread is not None:
|
73
|
+
self._interaction_thread.stop()
|
74
|
+
self._interaction_thread.join()
|
75
|
+
|
82
76
|
self._num_launch_attempts += 1
|
83
|
-
|
77
|
+
try:
|
78
|
+
self._launch_impl()
|
79
|
+
except Exception as error:
|
80
|
+
for line in self.get_logs().splitlines():
|
81
|
+
logging.error(line)
|
82
|
+
raise errors.SimulatorError(
|
83
|
+
'Exception caught in simulator. Please see the simulator logs '
|
84
|
+
'above for more details.'
|
85
|
+
) from error
|
86
|
+
|
87
|
+
# Start interaction thread.
|
88
|
+
if self._config.interaction_rate_sec > 0:
|
89
|
+
self._interaction_thread = InteractionThread(
|
90
|
+
self._get_screenshot_impl, self._config.interaction_rate_sec
|
91
|
+
)
|
92
|
+
self._interaction_thread.start()
|
84
93
|
|
85
94
|
@abc.abstractmethod
|
86
95
|
def _launch_impl(self) -> None:
|
@@ -139,9 +148,18 @@ class BaseSimulator(metaclass=abc.ABCMeta):
|
|
139
148
|
"""
|
140
149
|
raise NotImplementedError('This simulator does not support save_state()')
|
141
150
|
|
142
|
-
@abc.abstractmethod
|
143
151
|
def get_screenshot(self) -> np.ndarray:
|
144
|
-
"""Returns pixels representing the current screenshot of the simulator.
|
152
|
+
"""Returns pixels representing the current screenshot of the simulator."""
|
153
|
+
|
154
|
+
if self._config.interaction_rate_sec > 0:
|
155
|
+
assert self._interaction_thread is not None
|
156
|
+
return self._interaction_thread.screenshot() # Async mode.
|
157
|
+
else:
|
158
|
+
return self._get_screenshot_impl() # Sync mode.
|
159
|
+
|
160
|
+
@abc.abstractmethod
|
161
|
+
def _get_screenshot_impl(self) -> np.ndarray:
|
162
|
+
"""Actual implementation of `get_screenshot()`.
|
145
163
|
|
146
164
|
The output numpy array should have shape [height, width, num_channels] and
|
147
165
|
can be loaded into PIL using Image.fromarray(img, mode='RGB') and be saved
|
@@ -150,3 +168,41 @@ class BaseSimulator(metaclass=abc.ABCMeta):
|
|
150
168
|
|
151
169
|
def close(self):
|
152
170
|
"""Frees up resources allocated by this object."""
|
171
|
+
|
172
|
+
if self._interaction_thread is not None:
|
173
|
+
self._interaction_thread.stop()
|
174
|
+
self._interaction_thread.join()
|
175
|
+
|
176
|
+
|
177
|
+
class InteractionThread(threading.Thread):
|
178
|
+
"""A thread that gets screenshot in the background."""
|
179
|
+
|
180
|
+
def __init__(
|
181
|
+
self,
|
182
|
+
get_screenshot_fn: Callable[[], np.ndarray],
|
183
|
+
interaction_rate_sec: float,
|
184
|
+
):
|
185
|
+
super().__init__()
|
186
|
+
self._get_screenshot_fn = get_screenshot_fn
|
187
|
+
self._interaction_rate_sec = interaction_rate_sec
|
188
|
+
self._should_stop = threading.Event()
|
189
|
+
self._screenshot = self._get_screenshot_fn()
|
190
|
+
|
191
|
+
def run(self):
|
192
|
+
last_read = time.time()
|
193
|
+
while not self._should_stop.is_set():
|
194
|
+
self._screenshot = self._get_screenshot_fn()
|
195
|
+
now = time.time()
|
196
|
+
elapsed = now - last_read
|
197
|
+
last_read = now
|
198
|
+
sleep_time = self._interaction_rate_sec - elapsed
|
199
|
+
if sleep_time > 0.0:
|
200
|
+
time.sleep(sleep_time)
|
201
|
+
logging.info('InteractionThread.run() finished.')
|
202
|
+
|
203
|
+
def stop(self):
|
204
|
+
logging.info('Stopping InteractionThread.')
|
205
|
+
self._should_stop.set()
|
206
|
+
|
207
|
+
def screenshot(self) -> np.ndarray:
|
208
|
+
return self._screenshot
|
@@ -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.
|
@@ -13,14 +13,16 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
|
16
|
-
|
17
|
-
|
16
|
+
import itertools
|
17
|
+
import time
|
18
18
|
from unittest import mock
|
19
19
|
|
20
20
|
from absl.testing import absltest
|
21
|
+
from android_env.components import config_classes
|
21
22
|
from android_env.components import errors
|
22
23
|
# fake_simulator.FakeSimulator inherits from BaseSimulator, so there's no need
|
23
24
|
# to import it here explicitly.
|
25
|
+
from android_env.components.simulators import base_simulator
|
24
26
|
from android_env.components.simulators.fake import fake_simulator
|
25
27
|
import numpy as np
|
26
28
|
|
@@ -28,19 +30,25 @@ import numpy as np
|
|
28
30
|
class BaseSimulatorTest(absltest.TestCase):
|
29
31
|
|
30
32
|
def test_launch(self):
|
31
|
-
simulator = fake_simulator.FakeSimulator(
|
33
|
+
simulator = fake_simulator.FakeSimulator(
|
34
|
+
config_classes.FakeSimulatorConfig(screen_dimensions=(640, 480))
|
35
|
+
)
|
32
36
|
# The simulator should launch and not crash.
|
33
37
|
simulator.launch()
|
34
38
|
|
35
39
|
def test_launch_close(self):
|
36
|
-
simulator = fake_simulator.FakeSimulator(
|
40
|
+
simulator = fake_simulator.FakeSimulator(
|
41
|
+
config_classes.FakeSimulatorConfig()
|
42
|
+
)
|
37
43
|
# The simulator should launch and not crash.
|
38
44
|
simulator.launch()
|
39
45
|
# Closing the simulator should also not crash.
|
40
46
|
simulator.close()
|
41
47
|
|
42
48
|
def test_get_screenshot(self):
|
43
|
-
simulator = fake_simulator.FakeSimulator(
|
49
|
+
simulator = fake_simulator.FakeSimulator(
|
50
|
+
config_classes.FakeSimulatorConfig(screen_dimensions=(640, 480))
|
51
|
+
)
|
44
52
|
# The simulator should launch and not crash.
|
45
53
|
simulator.launch()
|
46
54
|
|
@@ -48,12 +56,126 @@ class BaseSimulatorTest(absltest.TestCase):
|
|
48
56
|
np.testing.assert_equal(screenshot.shape, [640, 480, 3])
|
49
57
|
|
50
58
|
def test_print_logs_on_exception(self):
|
51
|
-
simulator = fake_simulator.FakeSimulator(
|
52
|
-
|
53
|
-
|
59
|
+
simulator = fake_simulator.FakeSimulator(
|
60
|
+
config_classes.FakeSimulatorConfig()
|
61
|
+
)
|
62
|
+
with mock.patch.object(
|
63
|
+
simulator, 'get_logs'
|
64
|
+
) as mock_get_logs, mock.patch.object(
|
65
|
+
simulator, '_launch_impl', autospec=True
|
66
|
+
) as mock_launch:
|
54
67
|
mock_launch.side_effect = ValueError('Oh no!')
|
55
68
|
self.assertRaises(errors.SimulatorError, simulator.launch)
|
56
69
|
mock_get_logs.assert_called_once()
|
57
70
|
|
71
|
+
def test_get_screenshot_error_async(self):
|
72
|
+
"""An exception in the underlying interaction thread should bubble up."""
|
73
|
+
|
74
|
+
# Arrange.
|
75
|
+
mock_interaction_thread = mock.create_autospec(
|
76
|
+
base_simulator.InteractionThread
|
77
|
+
)
|
78
|
+
mock_interaction_thread.screenshot.side_effect = (
|
79
|
+
errors.ReadObservationError()
|
80
|
+
)
|
81
|
+
simulator = fake_simulator.FakeSimulator(
|
82
|
+
config_classes.FakeSimulatorConfig(interaction_rate_sec=0.5)
|
83
|
+
)
|
84
|
+
with mock.patch.object(
|
85
|
+
base_simulator,
|
86
|
+
'InteractionThread',
|
87
|
+
autospec=True,
|
88
|
+
return_value=mock_interaction_thread,
|
89
|
+
):
|
90
|
+
simulator.launch()
|
91
|
+
|
92
|
+
# Act & Assert.
|
93
|
+
self.assertRaises(errors.ReadObservationError, simulator.get_screenshot)
|
94
|
+
|
95
|
+
# Cleanup.
|
96
|
+
simulator.close()
|
97
|
+
|
98
|
+
def test_get_screenshot_faster_than_screenshot_impl(self):
|
99
|
+
"""Return same screenshot when step is faster than the interaction rate."""
|
100
|
+
|
101
|
+
# Arrange.
|
102
|
+
slow_rate = 0.5
|
103
|
+
simulator = fake_simulator.FakeSimulator(
|
104
|
+
config_classes.FakeSimulatorConfig(interaction_rate_sec=slow_rate)
|
105
|
+
)
|
106
|
+
|
107
|
+
# Act.
|
108
|
+
with mock.patch.object(
|
109
|
+
simulator, '_get_screenshot_impl', autospec=True
|
110
|
+
) as mock_get_screenshot_impl:
|
111
|
+
mock_get_screenshot_impl.side_effect = (
|
112
|
+
np.array(i, ndmin=3) for i in itertools.count(0, 1)
|
113
|
+
)
|
114
|
+
simulator.launch()
|
115
|
+
# Get two screenshots one after the other without pausing.
|
116
|
+
screenshot1 = simulator.get_screenshot()
|
117
|
+
screenshot2 = simulator.get_screenshot()
|
118
|
+
|
119
|
+
# Assert.
|
120
|
+
self.assertAlmostEqual(screenshot1[0][0][0], screenshot2[0][0][0])
|
121
|
+
|
122
|
+
# Cleanup.
|
123
|
+
simulator.close()
|
124
|
+
|
125
|
+
def test_get_screenshot_slower_than_screenshot_impl(self):
|
126
|
+
"""Return different screenshots when step slower than the interaction rate."""
|
127
|
+
|
128
|
+
# Arrange.
|
129
|
+
fast_rate = 0.01
|
130
|
+
simulator = fake_simulator.FakeSimulator(
|
131
|
+
config_classes.FakeSimulatorConfig(interaction_rate_sec=fast_rate)
|
132
|
+
)
|
133
|
+
|
134
|
+
# Act.
|
135
|
+
with mock.patch.object(
|
136
|
+
simulator, '_get_screenshot_impl', autospec=True
|
137
|
+
) as mock_get_screenshot_impl:
|
138
|
+
mock_get_screenshot_impl.side_effect = (
|
139
|
+
np.array(i, ndmin=3) for i in itertools.count(0, 1)
|
140
|
+
)
|
141
|
+
simulator.launch()
|
142
|
+
# Sleep for 500ms between two screenshots.
|
143
|
+
screenshot1 = simulator.get_screenshot()
|
144
|
+
time.sleep(0.5)
|
145
|
+
screenshot2 = simulator.get_screenshot()
|
146
|
+
|
147
|
+
# Assert.
|
148
|
+
self.assertNotEqual(screenshot1[0][0][0], screenshot2[0][0][0])
|
149
|
+
|
150
|
+
# Cleanup.
|
151
|
+
simulator.close()
|
152
|
+
|
153
|
+
def test_interaction_thread_closes_upon_relaunch(self):
|
154
|
+
"""Async interaction should kill the InteractionThread when relaunching."""
|
155
|
+
|
156
|
+
# Arrange.
|
157
|
+
simulator = fake_simulator.FakeSimulator(
|
158
|
+
config_classes.FakeSimulatorConfig(interaction_rate_sec=0.01)
|
159
|
+
)
|
160
|
+
mock_interaction_thread = mock.create_autospec(
|
161
|
+
base_simulator.InteractionThread
|
162
|
+
)
|
163
|
+
|
164
|
+
# Act & Assert.
|
165
|
+
with mock.patch.object(
|
166
|
+
base_simulator,
|
167
|
+
'InteractionThread',
|
168
|
+
autospec=True,
|
169
|
+
return_value=mock_interaction_thread,
|
170
|
+
):
|
171
|
+
simulator.launch()
|
172
|
+
mock_interaction_thread.stop.assert_not_called()
|
173
|
+
mock_interaction_thread.join.assert_not_called()
|
174
|
+
simulator.launch()
|
175
|
+
mock_interaction_thread.stop.assert_called_once()
|
176
|
+
mock_interaction_thread.join.assert_called_once()
|
177
|
+
simulator.close()
|
178
|
+
|
179
|
+
|
58
180
|
if __name__ == '__main__':
|
59
181
|
absltest.main()
|
@@ -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.
|
@@ -19,9 +19,9 @@ import glob
|
|
19
19
|
import os
|
20
20
|
import subprocess
|
21
21
|
import tempfile
|
22
|
-
from typing import Optional
|
23
22
|
|
24
23
|
from absl import logging
|
24
|
+
from android_env.components import config_classes
|
25
25
|
|
26
26
|
|
27
27
|
class EmulatorLauncher:
|
@@ -29,58 +29,13 @@ class EmulatorLauncher:
|
|
29
29
|
|
30
30
|
def __init__(
|
31
31
|
self,
|
32
|
-
|
33
|
-
|
34
|
-
adb_server_port: Optional[int] = None,
|
35
|
-
emulator_console_port: Optional[int] = None,
|
36
|
-
grpc_port: int = -1,
|
37
|
-
emulator_path: str = '',
|
38
|
-
android_sdk_root: str = '',
|
39
|
-
avd_name: str = '',
|
40
|
-
android_avd_home: str = '',
|
41
|
-
run_headless: bool = False,
|
42
|
-
kvm_device: str = '/dev/kvm',
|
43
|
-
gpu_mode: str = 'swiftshader_indirect',
|
44
|
-
tmp_dir: str = '',
|
45
|
-
snapshot_name: str = '',
|
46
|
-
restrict_network: bool = False,
|
32
|
+
config: config_classes.EmulatorLauncherConfig,
|
33
|
+
adb_controller_config: config_classes.AdbControllerConfig,
|
47
34
|
):
|
48
|
-
"""Launches an emulator.
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
adb_port: ADB port for the Android device.
|
53
|
-
adb_server_port: Port of the ADB server deamon.
|
54
|
-
emulator_console_port: Port for telnet communication with the emulator.
|
55
|
-
grpc_port: Port for gRPC communication with the emulator.
|
56
|
-
emulator_path: Path to the emulator binary.
|
57
|
-
android_sdk_root: Root directory of the Android SDK.
|
58
|
-
avd_name: Name of the AVD.
|
59
|
-
android_avd_home: Local directory for AVDs.
|
60
|
-
run_headless: Whether to run in headless mode.
|
61
|
-
kvm_device: Path to the KVM device.
|
62
|
-
gpu_mode: GPU mode override. Supported values are listed at:
|
63
|
-
https://developer.android.com/studio/run/emulator-acceleration#accel-graphics
|
64
|
-
tmp_dir: Path to directory which will hold temporary files.
|
65
|
-
snapshot_name: Name of the snapshot to load.
|
66
|
-
restrict_network: if True, will disable networking on the device. This
|
67
|
-
option is only available for emulator version > 31.3.9 (June 2022).
|
68
|
-
"""
|
69
|
-
|
70
|
-
self._adb_path = os.path.expandvars(adb_path)
|
71
|
-
self._adb_port = adb_port
|
72
|
-
self._adb_server_port = adb_server_port
|
73
|
-
self._emulator_console_port = emulator_console_port
|
74
|
-
self._grpc_port = grpc_port
|
75
|
-
self._emulator_path = os.path.expandvars(emulator_path)
|
76
|
-
self._android_sdk_root = os.path.expandvars(android_sdk_root)
|
77
|
-
self._avd_name = avd_name
|
78
|
-
self._android_avd_home = android_avd_home
|
79
|
-
self._run_headless = run_headless
|
80
|
-
self._kvm_device = kvm_device
|
81
|
-
self._gpu_mode = gpu_mode
|
82
|
-
self._snapshot_name = snapshot_name
|
83
|
-
self._restrict_network = restrict_network
|
35
|
+
"""Launches an emulator."""
|
36
|
+
|
37
|
+
self._config = config
|
38
|
+
self._adb_controller_config = adb_controller_config
|
84
39
|
|
85
40
|
self._emulator = None
|
86
41
|
self._emulator_output = None
|
@@ -88,9 +43,10 @@ class EmulatorLauncher:
|
|
88
43
|
|
89
44
|
# Create directory for tmp files.
|
90
45
|
# Note: this will be deleted once EmulatorLauncher instance is cleaned up.
|
91
|
-
os.makedirs(tmp_dir, exist_ok=True)
|
46
|
+
os.makedirs(config.tmp_dir, exist_ok=True)
|
92
47
|
self._local_tmp_dir_handle = tempfile.TemporaryDirectory(
|
93
|
-
dir=tmp_dir, prefix='simulator_instance_'
|
48
|
+
dir=config.tmp_dir, prefix='simulator_instance_'
|
49
|
+
)
|
94
50
|
self._local_tmp_dir = self._local_tmp_dir_handle.name
|
95
51
|
self._logfile_path = os.path.join(self._local_tmp_dir, 'emulator_output')
|
96
52
|
logging.info('Simulator local_tmp_dir: %s', self._local_tmp_dir)
|
@@ -101,23 +57,28 @@ class EmulatorLauncher:
|
|
101
57
|
def launch_emulator_process(self) -> None:
|
102
58
|
"""Launches the emulator."""
|
103
59
|
|
104
|
-
logging.info('Booting new emulator
|
60
|
+
logging.info('Booting new emulator: %s', self._config.emulator_path)
|
105
61
|
|
106
62
|
# Set necessary environment variables.
|
107
|
-
base_lib_dir = self.
|
63
|
+
base_lib_dir = self._config.emulator_path[:-8] + 'lib64/'
|
108
64
|
ld_library_path = ':'.join([
|
109
65
|
base_lib_dir + 'x11/', base_lib_dir + 'qt/lib/',
|
110
66
|
base_lib_dir + 'gles_swiftshader/', base_lib_dir
|
111
67
|
])
|
112
68
|
extra_env_vars = {
|
113
69
|
'ANDROID_HOME': '',
|
114
|
-
'ANDROID_SDK_ROOT': self.
|
115
|
-
'ANDROID_AVD_HOME': self.
|
116
|
-
'ANDROID_EMULATOR_KVM_DEVICE': self.
|
117
|
-
'ANDROID_ADB_SERVER_PORT': str(
|
70
|
+
'ANDROID_SDK_ROOT': self._config.android_sdk_root,
|
71
|
+
'ANDROID_AVD_HOME': self._config.android_avd_home,
|
72
|
+
'ANDROID_EMULATOR_KVM_DEVICE': self._config.kvm_device,
|
73
|
+
'ANDROID_ADB_SERVER_PORT': str(
|
74
|
+
self._adb_controller_config.adb_server_port
|
75
|
+
),
|
118
76
|
'LD_LIBRARY_PATH': ld_library_path,
|
119
|
-
'QT_XKB_CONFIG_ROOT': str(
|
77
|
+
'QT_XKB_CONFIG_ROOT': str(
|
78
|
+
self._config.emulator_path[:-8] + 'qt_config/'
|
79
|
+
),
|
120
80
|
'ANDROID_EMU_ENABLE_CRASH_REPORTING': '1',
|
81
|
+
'SHOW_PERF_STATS': str(1 if self._config.show_perf_stats else 0),
|
121
82
|
}
|
122
83
|
logging.info('extra_env_vars: %s',
|
123
84
|
' '.join(f'{k}={v}' for k, v in extra_env_vars.items()))
|
@@ -125,31 +86,51 @@ class EmulatorLauncher:
|
|
125
86
|
env_vars.update(extra_env_vars)
|
126
87
|
|
127
88
|
# Compile command.
|
128
|
-
grpc_port =
|
129
|
-
|
130
|
-
|
89
|
+
grpc_port = (
|
90
|
+
['-grpc', str(self._config.grpc_port)]
|
91
|
+
if self._config.grpc_port >= 0
|
92
|
+
else []
|
93
|
+
)
|
94
|
+
run_headless = (
|
95
|
+
['-no-skin', '-no-window'] if self._config.run_headless else []
|
96
|
+
)
|
97
|
+
ports = [
|
98
|
+
'-ports',
|
99
|
+
'%s,%s' % (self._config.emulator_console_port, self._config.adb_port),
|
100
|
+
]
|
131
101
|
snapshot = [
|
132
|
-
'-snapshot',
|
133
|
-
|
102
|
+
'-snapshot',
|
103
|
+
self._config.snapshot_name,
|
104
|
+
'-feature',
|
105
|
+
'AllowSnapshotMigration,MigratableSnapshotSave',
|
134
106
|
]
|
135
|
-
snapshot = snapshot if self.
|
107
|
+
snapshot = snapshot if self._config.snapshot_name else ['-no-snapshot']
|
136
108
|
restrict_network_args = [
|
137
109
|
'-network-user-mode-options', 'restrict=y', '-wifi-user-mode-options',
|
138
110
|
'restrict=y'
|
139
111
|
]
|
140
|
-
network_args =
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
112
|
+
network_args = (
|
113
|
+
restrict_network_args if self._config.restrict_network else []
|
114
|
+
)
|
115
|
+
command = (
|
116
|
+
[
|
117
|
+
self._config.emulator_path,
|
118
|
+
'-adb-path',
|
119
|
+
self._adb_controller_config.adb_path,
|
120
|
+
'-gpu',
|
121
|
+
self._config.gpu_mode,
|
122
|
+
'-no-audio',
|
123
|
+
'-show-kernel',
|
124
|
+
'-verbose',
|
125
|
+
'-avd',
|
126
|
+
self._config.avd_name,
|
127
|
+
]
|
128
|
+
+ grpc_port
|
129
|
+
+ run_headless
|
130
|
+
+ ports
|
131
|
+
+ snapshot
|
132
|
+
+ network_args
|
133
|
+
)
|
153
134
|
logging.info('Emulator launch command: %s', ' '.join(command))
|
154
135
|
# Prepare logfile.
|
155
136
|
self._emulator_output = open(self._logfile_path, 'wb')
|