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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. android_env/__init__.py +1 -1
  2. android_env/components/__init__.py +1 -1
  3. android_env/components/a11y/__init__.py +15 -0
  4. android_env/components/a11y/a11y_events.py +118 -0
  5. android_env/components/a11y/a11y_events_test.py +173 -0
  6. android_env/components/a11y/a11y_forests.py +128 -0
  7. android_env/components/a11y/a11y_forests_test.py +237 -0
  8. android_env/components/a11y/a11y_servicer.py +199 -0
  9. android_env/components/a11y/a11y_servicer_test.py +224 -0
  10. android_env/components/action_fns.py +132 -0
  11. android_env/components/action_fns_test.py +227 -0
  12. android_env/components/action_type.py +26 -3
  13. android_env/components/adb_call_parser.py +233 -185
  14. android_env/components/adb_call_parser_test.py +165 -163
  15. android_env/components/adb_controller.py +19 -28
  16. android_env/components/adb_controller_test.py +100 -9
  17. android_env/components/adb_log_stream.py +3 -3
  18. android_env/components/adb_log_stream_test.py +1 -1
  19. android_env/components/app_screen_checker.py +15 -13
  20. android_env/components/app_screen_checker_test.py +1 -1
  21. android_env/components/config_classes.py +203 -0
  22. android_env/components/coordinator.py +53 -338
  23. android_env/components/coordinator_test.py +26 -283
  24. android_env/components/device_settings.py +174 -0
  25. android_env/components/device_settings_test.py +228 -0
  26. android_env/components/dumpsys_thread.py +3 -4
  27. android_env/components/dumpsys_thread_test.py +1 -1
  28. android_env/components/errors.py +2 -5
  29. android_env/components/errors_test.py +1 -1
  30. android_env/components/log_stream.py +2 -2
  31. android_env/components/log_stream_test.py +1 -1
  32. android_env/components/logcat_thread.py +9 -8
  33. android_env/components/logcat_thread_test.py +2 -3
  34. android_env/components/{utils.py → pixel_fns.py} +19 -20
  35. android_env/components/{utils_test.py → pixel_fns_test.py} +20 -15
  36. android_env/components/setup_step_interpreter.py +45 -37
  37. android_env/components/setup_step_interpreter_test.py +1 -1
  38. android_env/components/simulators/__init__.py +1 -1
  39. android_env/components/simulators/base_simulator.py +79 -23
  40. android_env/components/simulators/base_simulator_test.py +131 -9
  41. android_env/components/simulators/emulator/__init__.py +1 -1
  42. android_env/components/simulators/emulator/emulator_launcher.py +62 -81
  43. android_env/components/simulators/emulator/emulator_launcher_test.py +120 -43
  44. android_env/components/simulators/emulator/emulator_simulator.py +111 -98
  45. android_env/components/simulators/emulator/emulator_simulator_test.py +174 -138
  46. android_env/components/simulators/fake/__init__.py +1 -1
  47. android_env/components/simulators/fake/fake_simulator.py +9 -17
  48. android_env/components/simulators/fake/fake_simulator_test.py +23 -8
  49. android_env/components/specs.py +1 -1
  50. android_env/components/specs_test.py +1 -1
  51. android_env/components/task_manager.py +26 -31
  52. android_env/components/task_manager_test.py +1 -18
  53. android_env/env_interface.py +1 -17
  54. android_env/environment.py +27 -17
  55. android_env/environment_test.py +51 -25
  56. android_env/loader.py +57 -43
  57. android_env/loader_test.py +115 -35
  58. android_env/proto/__init__.py +1 -1
  59. android_env/proto/a11y/__init__.py +15 -0
  60. android_env/proto/a11y/a11y.proto +75 -0
  61. android_env/proto/a11y/a11y_pb2.py +54 -0
  62. android_env/proto/a11y/a11y_pb2.pyi +49 -0
  63. android_env/proto/a11y/a11y_pb2_grpc.py +202 -0
  64. android_env/proto/a11y/android_accessibility_action.proto +32 -0
  65. android_env/proto/a11y/android_accessibility_action_pb2.py +37 -0
  66. android_env/proto/a11y/android_accessibility_action_pb2.pyi +13 -0
  67. android_env/proto/a11y/android_accessibility_action_pb2_grpc.py +24 -0
  68. android_env/proto/a11y/android_accessibility_forest.proto +29 -0
  69. android_env/proto/a11y/android_accessibility_forest_pb2.py +38 -0
  70. android_env/proto/a11y/android_accessibility_forest_pb2.pyi +13 -0
  71. android_env/proto/a11y/android_accessibility_forest_pb2_grpc.py +24 -0
  72. android_env/proto/a11y/android_accessibility_node_info.proto +122 -0
  73. android_env/proto/a11y/android_accessibility_node_info_clickable_span.proto +49 -0
  74. android_env/proto/a11y/android_accessibility_node_info_clickable_span_pb2.py +39 -0
  75. android_env/proto/a11y/android_accessibility_node_info_clickable_span_pb2.pyi +28 -0
  76. android_env/proto/a11y/android_accessibility_node_info_clickable_span_pb2_grpc.py +24 -0
  77. android_env/proto/a11y/android_accessibility_node_info_pb2.py +42 -0
  78. android_env/proto/a11y/android_accessibility_node_info_pb2.pyi +75 -0
  79. android_env/proto/a11y/android_accessibility_node_info_pb2_grpc.py +24 -0
  80. android_env/proto/a11y/android_accessibility_tree.proto +29 -0
  81. android_env/proto/a11y/android_accessibility_tree_pb2.py +38 -0
  82. android_env/proto/a11y/android_accessibility_tree_pb2.pyi +13 -0
  83. android_env/proto/a11y/android_accessibility_tree_pb2_grpc.py +24 -0
  84. android_env/proto/a11y/android_accessibility_window_info.proto +84 -0
  85. android_env/proto/a11y/android_accessibility_window_info_pb2.py +41 -0
  86. android_env/proto/a11y/android_accessibility_window_info_pb2.pyi +48 -0
  87. android_env/proto/a11y/android_accessibility_window_info_pb2_grpc.py +24 -0
  88. android_env/proto/a11y/rect.proto +30 -0
  89. android_env/proto/a11y/rect_pb2.py +37 -0
  90. android_env/proto/a11y/rect_pb2.pyi +17 -0
  91. android_env/proto/a11y/rect_pb2_grpc.py +24 -0
  92. android_env/proto/adb.proto +13 -1
  93. android_env/proto/adb_pb2.py +120 -107
  94. android_env/proto/adb_pb2.pyi +396 -0
  95. android_env/proto/adb_pb2_grpc.py +20 -0
  96. android_env/proto/emulator_controller.proto +1 -1
  97. android_env/proto/emulator_controller_pb2.py +142 -131
  98. android_env/proto/emulator_controller_pb2.pyi +672 -0
  99. android_env/proto/emulator_controller_pb2_grpc.py +497 -136
  100. android_env/proto/snapshot.proto +1 -1
  101. android_env/proto/snapshot_pb2.py +30 -19
  102. android_env/proto/snapshot_pb2.pyi +117 -0
  103. android_env/proto/snapshot_pb2_grpc.py +20 -0
  104. android_env/proto/snapshot_service.proto +1 -1
  105. android_env/proto/snapshot_service_pb2.py +36 -25
  106. android_env/proto/snapshot_service_pb2.pyi +86 -0
  107. android_env/proto/snapshot_service_pb2_grpc.py +119 -28
  108. android_env/proto/state.proto +1 -1
  109. android_env/proto/state_pb2.py +46 -35
  110. android_env/proto/state_pb2.pyi +85 -0
  111. android_env/proto/state_pb2_grpc.py +20 -0
  112. android_env/proto/task.proto +4 -1
  113. android_env/proto/task_pb2.py +41 -30
  114. android_env/proto/task_pb2.pyi +160 -0
  115. android_env/proto/task_pb2_grpc.py +20 -0
  116. android_env/wrappers/__init__.py +1 -1
  117. android_env/wrappers/a11y_grpc_wrapper.py +500 -0
  118. android_env/wrappers/a11y_grpc_wrapper_test.py +849 -0
  119. android_env/wrappers/base_wrapper.py +1 -5
  120. android_env/wrappers/base_wrapper_test.py +1 -7
  121. android_env/wrappers/discrete_action_wrapper.py +15 -14
  122. android_env/wrappers/discrete_action_wrapper_test.py +1 -1
  123. android_env/wrappers/flat_interface_wrapper.py +5 -5
  124. android_env/wrappers/flat_interface_wrapper_test.py +1 -1
  125. android_env/wrappers/float_pixels_wrapper.py +5 -4
  126. android_env/wrappers/float_pixels_wrapper_test.py +1 -1
  127. android_env/wrappers/gym_wrapper.py +1 -1
  128. android_env/wrappers/gym_wrapper_test.py +1 -1
  129. android_env/wrappers/image_rescale_wrapper.py +13 -10
  130. android_env/wrappers/image_rescale_wrapper_test.py +1 -1
  131. android_env/wrappers/last_action_wrapper.py +5 -4
  132. android_env/wrappers/last_action_wrapper_test.py +1 -1
  133. android_env/wrappers/rate_limit_wrapper.py +1 -1
  134. android_env/wrappers/rate_limit_wrapper_test.py +1 -1
  135. android_env/wrappers/tap_action_wrapper.py +12 -12
  136. android_env/wrappers/tap_action_wrapper_test.py +49 -14
  137. {android_env-1.2.2.dist-info → android_env-1.2.3.dist-info}/METADATA +14 -16
  138. android_env-1.2.3.dist-info/RECORD +141 -0
  139. {android_env-1.2.2.dist-info → android_env-1.2.3.dist-info}/WHEEL +1 -1
  140. android_env-1.2.2.dist-info/RECORD +0 -88
  141. {android_env-1.2.2.dist-info → android_env-1.2.3.dist-info/licenses}/LICENSE +0 -0
  142. {android_env-1.2.2.dist-info → android_env-1.2.3.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  # coding=utf-8
2
- # Copyright 2023 DeepMind Technologies Limited.
2
+ # Copyright 2024 DeepMind Technologies Limited.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  # coding=utf-8
2
- # Copyright 2023 DeepMind Technologies Limited.
2
+ # Copyright 2024 DeepMind Technologies Limited.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -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, verbose_logs: bool = False):
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
- verbose_logs: If true, the log stream of the simulator will be verbose.
43
+ config: Settings for this simulator.
55
44
  """
56
45
 
57
- self._verbose_logs = verbose_logs
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
- self._launch_impl()
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 2023 DeepMind Technologies Limited.
2
+ # Copyright 2024 DeepMind Technologies Limited.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -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
- """Tests for base_simulator."""
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(screen_dimensions=(640, 480))
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(screen_dimensions=(640, 480))
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
- with mock.patch.object(simulator, 'get_logs') as mock_get_logs, \
53
- mock.patch.object(simulator, '_launch_impl', autospec=True) as mock_launch:
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 2023 DeepMind Technologies Limited.
2
+ # Copyright 2024 DeepMind Technologies Limited.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  # coding=utf-8
2
- # Copyright 2023 DeepMind Technologies Limited.
2
+ # Copyright 2024 DeepMind Technologies Limited.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -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
- adb_path: str,
33
- adb_port: Optional[int] = None,
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
- Args:
51
- adb_path: Filesystem path to `adb` executable binary.
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 [%s]', self._emulator_path)
60
+ logging.info('Booting new emulator: %s', self._config.emulator_path)
105
61
 
106
62
  # Set necessary environment variables.
107
- base_lib_dir = self._emulator_path[:-8] + 'lib64/'
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._android_sdk_root,
115
- 'ANDROID_AVD_HOME': self._android_avd_home,
116
- 'ANDROID_EMULATOR_KVM_DEVICE': self._kvm_device,
117
- 'ANDROID_ADB_SERVER_PORT': str(self._adb_server_port),
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(self._emulator_path[:-8] + 'qt_config/'),
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 = ['-grpc', str(self._grpc_port)] if self._grpc_port >= 0 else []
129
- run_headless = ['-no-skin', '-no-window'] if self._run_headless else []
130
- ports = ['-ports', '%s,%s' % (self._emulator_console_port, self._adb_port)]
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', self._snapshot_name, '-feature',
133
- 'AllowSnapshotMigration,MigratableSnapshotSave'
102
+ '-snapshot',
103
+ self._config.snapshot_name,
104
+ '-feature',
105
+ 'AllowSnapshotMigration,MigratableSnapshotSave',
134
106
  ]
135
- snapshot = snapshot if self._snapshot_name else ['-no-snapshot']
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 = restrict_network_args if self._restrict_network else []
141
- command = [
142
- self._emulator_path,
143
- '-adb-path',
144
- self._adb_path,
145
- '-gpu',
146
- self._gpu_mode,
147
- '-no-audio',
148
- '-show-kernel',
149
- '-verbose',
150
- '-avd',
151
- self._avd_name,
152
- ] + grpc_port + run_headless + ports + snapshot + network_args
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')