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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. android_env/__init__.py +1 -1
  2. android_env/components/__init__.py +1 -1
  3. android_env/components/a11y/__init__.py +15 -0
  4. android_env/components/a11y/a11y_events.py +118 -0
  5. android_env/components/a11y/a11y_events_test.py +173 -0
  6. android_env/components/a11y/a11y_forests.py +128 -0
  7. android_env/components/a11y/a11y_forests_test.py +237 -0
  8. android_env/components/a11y/a11y_servicer.py +199 -0
  9. android_env/components/a11y/a11y_servicer_test.py +224 -0
  10. android_env/components/action_fns.py +132 -0
  11. android_env/components/action_fns_test.py +227 -0
  12. android_env/components/action_type.py +26 -3
  13. android_env/components/adb_call_parser.py +239 -196
  14. android_env/components/adb_call_parser_test.py +179 -209
  15. android_env/components/adb_controller.py +90 -52
  16. android_env/components/adb_controller_test.py +187 -16
  17. android_env/components/adb_log_stream.py +17 -5
  18. android_env/components/adb_log_stream_test.py +17 -3
  19. android_env/components/app_screen_checker.py +17 -15
  20. android_env/components/app_screen_checker_test.py +7 -8
  21. android_env/components/config_classes.py +203 -0
  22. android_env/components/coordinator.py +102 -338
  23. android_env/components/coordinator_test.py +59 -199
  24. android_env/components/device_settings.py +174 -0
  25. android_env/components/device_settings_test.py +228 -0
  26. android_env/components/dumpsys_thread.py +3 -4
  27. android_env/components/dumpsys_thread_test.py +1 -1
  28. android_env/components/errors.py +52 -10
  29. android_env/components/errors_test.py +110 -0
  30. android_env/components/log_stream.py +7 -5
  31. android_env/components/log_stream_test.py +1 -1
  32. android_env/components/logcat_thread.py +9 -8
  33. android_env/components/logcat_thread_test.py +3 -4
  34. android_env/components/{utils.py → pixel_fns.py} +20 -20
  35. android_env/components/{utils_test.py → pixel_fns_test.py} +20 -15
  36. android_env/components/setup_step_interpreter.py +47 -39
  37. android_env/components/setup_step_interpreter_test.py +4 -4
  38. android_env/components/simulators/__init__.py +1 -1
  39. android_env/components/simulators/base_simulator.py +116 -44
  40. android_env/components/simulators/base_simulator_test.py +131 -9
  41. android_env/components/simulators/emulator/__init__.py +1 -1
  42. android_env/components/simulators/emulator/emulator_launcher.py +67 -77
  43. android_env/components/simulators/emulator/emulator_launcher_test.py +153 -49
  44. android_env/components/simulators/emulator/emulator_simulator.py +276 -95
  45. android_env/components/simulators/emulator/emulator_simulator_test.py +314 -89
  46. android_env/components/simulators/fake/__init__.py +1 -1
  47. android_env/components/simulators/fake/fake_simulator.py +17 -25
  48. android_env/components/simulators/fake/fake_simulator_test.py +29 -12
  49. android_env/components/specs.py +18 -28
  50. android_env/components/specs_test.py +1 -44
  51. android_env/components/task_manager.py +48 -48
  52. android_env/components/task_manager_test.py +71 -60
  53. android_env/env_interface.py +37 -23
  54. android_env/environment.py +83 -51
  55. android_env/environment_test.py +68 -29
  56. android_env/loader.py +57 -43
  57. android_env/loader_test.py +115 -35
  58. android_env/proto/__init__.py +1 -1
  59. android_env/proto/a11y/__init__.py +15 -0
  60. android_env/proto/a11y/a11y.proto +75 -0
  61. android_env/proto/a11y/a11y_pb2.py +54 -0
  62. android_env/proto/a11y/a11y_pb2.pyi +49 -0
  63. android_env/proto/a11y/a11y_pb2_grpc.py +202 -0
  64. android_env/proto/a11y/android_accessibility_action.proto +32 -0
  65. android_env/proto/a11y/android_accessibility_action_pb2.py +37 -0
  66. android_env/proto/a11y/android_accessibility_action_pb2.pyi +13 -0
  67. android_env/proto/a11y/android_accessibility_action_pb2_grpc.py +24 -0
  68. android_env/proto/a11y/android_accessibility_forest.proto +29 -0
  69. android_env/proto/a11y/android_accessibility_forest_pb2.py +38 -0
  70. android_env/proto/a11y/android_accessibility_forest_pb2.pyi +13 -0
  71. android_env/proto/a11y/android_accessibility_forest_pb2_grpc.py +24 -0
  72. android_env/proto/a11y/android_accessibility_node_info.proto +122 -0
  73. android_env/proto/a11y/android_accessibility_node_info_clickable_span.proto +49 -0
  74. android_env/proto/a11y/android_accessibility_node_info_clickable_span_pb2.py +39 -0
  75. android_env/proto/a11y/android_accessibility_node_info_clickable_span_pb2.pyi +28 -0
  76. android_env/proto/a11y/android_accessibility_node_info_clickable_span_pb2_grpc.py +24 -0
  77. android_env/proto/a11y/android_accessibility_node_info_pb2.py +42 -0
  78. android_env/proto/a11y/android_accessibility_node_info_pb2.pyi +75 -0
  79. android_env/proto/a11y/android_accessibility_node_info_pb2_grpc.py +24 -0
  80. android_env/proto/a11y/android_accessibility_tree.proto +29 -0
  81. android_env/proto/a11y/android_accessibility_tree_pb2.py +38 -0
  82. android_env/proto/a11y/android_accessibility_tree_pb2.pyi +13 -0
  83. android_env/proto/a11y/android_accessibility_tree_pb2_grpc.py +24 -0
  84. android_env/proto/a11y/android_accessibility_window_info.proto +84 -0
  85. android_env/proto/a11y/android_accessibility_window_info_pb2.py +41 -0
  86. android_env/proto/a11y/android_accessibility_window_info_pb2.pyi +48 -0
  87. android_env/proto/a11y/android_accessibility_window_info_pb2_grpc.py +24 -0
  88. android_env/proto/a11y/rect.proto +30 -0
  89. android_env/proto/a11y/rect_pb2.py +37 -0
  90. android_env/proto/a11y/rect_pb2.pyi +17 -0
  91. android_env/proto/a11y/rect_pb2_grpc.py +24 -0
  92. android_env/proto/adb.proto +17 -6
  93. android_env/proto/adb_pb2.py +120 -107
  94. android_env/proto/adb_pb2.pyi +396 -0
  95. android_env/proto/adb_pb2_grpc.py +20 -0
  96. android_env/proto/emulator_controller.proto +68 -63
  97. android_env/proto/emulator_controller_pb2.py +142 -131
  98. android_env/proto/emulator_controller_pb2.pyi +672 -0
  99. android_env/proto/emulator_controller_pb2_grpc.py +505 -142
  100. android_env/proto/snapshot.proto +169 -0
  101. android_env/proto/snapshot_pb2.py +47 -0
  102. android_env/proto/snapshot_pb2.pyi +117 -0
  103. android_env/proto/snapshot_pb2_grpc.py +24 -0
  104. android_env/proto/snapshot_service.proto +289 -0
  105. android_env/proto/snapshot_service_pb2.py +54 -0
  106. android_env/proto/snapshot_service_pb2.pyi +86 -0
  107. android_env/proto/snapshot_service_pb2_grpc.py +487 -0
  108. android_env/proto/state.proto +63 -0
  109. android_env/proto/state_pb2.py +63 -0
  110. android_env/proto/state_pb2.pyi +85 -0
  111. android_env/proto/state_pb2_grpc.py +24 -0
  112. android_env/proto/task.proto +5 -1
  113. android_env/proto/task_pb2.py +42 -31
  114. android_env/proto/task_pb2.pyi +160 -0
  115. android_env/proto/task_pb2_grpc.py +20 -0
  116. android_env/wrappers/__init__.py +1 -1
  117. android_env/wrappers/a11y_grpc_wrapper.py +500 -0
  118. android_env/wrappers/a11y_grpc_wrapper_test.py +849 -0
  119. android_env/wrappers/base_wrapper.py +34 -13
  120. android_env/wrappers/base_wrapper_test.py +22 -16
  121. android_env/wrappers/discrete_action_wrapper.py +18 -17
  122. android_env/wrappers/discrete_action_wrapper_test.py +4 -4
  123. android_env/wrappers/flat_interface_wrapper.py +5 -5
  124. android_env/wrappers/flat_interface_wrapper_test.py +7 -11
  125. android_env/wrappers/float_pixels_wrapper.py +9 -10
  126. android_env/wrappers/float_pixels_wrapper_test.py +3 -3
  127. android_env/wrappers/gym_wrapper.py +19 -13
  128. android_env/wrappers/gym_wrapper_test.py +3 -5
  129. android_env/wrappers/image_rescale_wrapper.py +18 -21
  130. android_env/wrappers/image_rescale_wrapper_test.py +25 -37
  131. android_env/wrappers/last_action_wrapper.py +16 -13
  132. android_env/wrappers/last_action_wrapper_test.py +44 -51
  133. android_env/wrappers/rate_limit_wrapper.py +6 -3
  134. android_env/wrappers/rate_limit_wrapper_test.py +22 -1
  135. android_env/wrappers/tap_action_wrapper.py +16 -17
  136. android_env/wrappers/tap_action_wrapper_test.py +51 -16
  137. {android_env-1.2.1.dist-info → android_env-1.2.3.dist-info}/METADATA +14 -18
  138. android_env-1.2.3.dist-info/RECORD +141 -0
  139. {android_env-1.2.1.dist-info → android_env-1.2.3.dist-info}/WHEEL +1 -1
  140. android_env/proto/raw_observation.proto +0 -39
  141. android_env/proto/raw_observation_pb2.py +0 -27
  142. android_env/proto/raw_observation_pb2_grpc.py +0 -4
  143. android_env-1.2.1.dist-info/RECORD +0 -81
  144. {android_env-1.2.1.dist-info → android_env-1.2.3.dist-info/licenses}/LICENSE +0 -0
  145. {android_env-1.2.1.dist-info → android_env-1.2.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,228 @@
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
+ from unittest import mock
17
+
18
+ from absl.testing import absltest
19
+ from absl.testing import parameterized
20
+ from android_env.components import config_classes
21
+ from android_env.components import device_settings as device_settings_lib
22
+ from android_env.components.simulators import base_simulator
23
+ import numpy as np
24
+
25
+
26
+ class DeviceSettingsTest(parameterized.TestCase):
27
+
28
+ def test_screen_size_before_update(self):
29
+ """The screen size should be 0x0 without calling `update()`."""
30
+
31
+ # Arrange.
32
+ simulator = mock.create_autospec(base_simulator.BaseSimulator)
33
+ device_settings = device_settings_lib.DeviceSettings(simulator)
34
+
35
+ # Act.
36
+ height = device_settings.screen_height()
37
+ width = device_settings.screen_width()
38
+
39
+ # Assert.
40
+ self.assertEqual(height, 0)
41
+ self.assertEqual(width, 0)
42
+
43
+ def test_screen_size_after_update(self):
44
+ """The screen size should be set after calling `update()`."""
45
+
46
+ # Arrange.
47
+ simulator = mock.create_autospec(base_simulator.BaseSimulator)
48
+ simulator.get_screenshot.return_value = np.random.randint(
49
+ low=0, high=255, size=(123, 456, 3), dtype=np.uint8
50
+ )
51
+ adb_controller = simulator.create_adb_controller.return_value
52
+ adb_controller.execute_command.return_value = b''
53
+ device_settings = device_settings_lib.DeviceSettings(simulator)
54
+
55
+ # Act.
56
+ device_settings.update(config_classes.DeviceSettingsConfig())
57
+ height = device_settings.screen_height()
58
+ width = device_settings.screen_width()
59
+
60
+ # Assert.
61
+ self.assertEqual(height, 123)
62
+ self.assertEqual(width, 456)
63
+
64
+ @parameterized.named_parameters(
65
+ (
66
+ 'show_touches',
67
+ config_classes.DeviceSettingsConfig(show_touches=True),
68
+ mock.call(
69
+ ['shell', 'settings', 'put', 'system', 'show_touches', '1'],
70
+ timeout=None,
71
+ ),
72
+ ),
73
+ (
74
+ 'show_touches_false',
75
+ config_classes.DeviceSettingsConfig(show_touches=False),
76
+ mock.call(
77
+ ['shell', 'settings', 'put', 'system', 'show_touches', '0'],
78
+ timeout=None,
79
+ ),
80
+ ),
81
+ (
82
+ 'show_pointer_location',
83
+ config_classes.DeviceSettingsConfig(show_pointer_location=True),
84
+ mock.call(
85
+ ['shell', 'settings', 'put', 'system', 'pointer_location', '1'],
86
+ timeout=None,
87
+ ),
88
+ ),
89
+ (
90
+ 'show_pointer_location_false',
91
+ config_classes.DeviceSettingsConfig(show_pointer_location=False),
92
+ mock.call(
93
+ ['shell', 'settings', 'put', 'system', 'pointer_location', '0'],
94
+ timeout=None,
95
+ ),
96
+ ),
97
+ (
98
+ 'show_navigation_and_status',
99
+ config_classes.DeviceSettingsConfig(
100
+ show_navigation_bar=True, show_status_bar=True
101
+ ),
102
+ mock.call(
103
+ ['shell', 'settings', 'put', 'global', 'policy_control', 'null*'],
104
+ timeout=None,
105
+ ),
106
+ ),
107
+ (
108
+ 'show_navigation_and_no_status',
109
+ config_classes.DeviceSettingsConfig(
110
+ show_navigation_bar=True, show_status_bar=False
111
+ ),
112
+ mock.call(
113
+ [
114
+ 'shell',
115
+ 'settings',
116
+ 'put',
117
+ 'global',
118
+ 'policy_control',
119
+ 'immersive.status=*',
120
+ ],
121
+ timeout=None,
122
+ ),
123
+ ),
124
+ (
125
+ 'show_no_navigation_and_status',
126
+ config_classes.DeviceSettingsConfig(
127
+ show_navigation_bar=False, show_status_bar=True
128
+ ),
129
+ mock.call(
130
+ [
131
+ 'shell',
132
+ 'settings',
133
+ 'put',
134
+ 'global',
135
+ 'policy_control',
136
+ 'immersive.navigation=*',
137
+ ],
138
+ timeout=None,
139
+ ),
140
+ ),
141
+ (
142
+ 'show_no_navigation_and_no_status',
143
+ config_classes.DeviceSettingsConfig(
144
+ show_navigation_bar=False, show_status_bar=False
145
+ ),
146
+ mock.call(
147
+ [
148
+ 'shell',
149
+ 'settings',
150
+ 'put',
151
+ 'global',
152
+ 'policy_control',
153
+ 'immersive.full=*',
154
+ ],
155
+ timeout=None,
156
+ ),
157
+ ),
158
+ )
159
+ def test_update(
160
+ self, settings: config_classes.DeviceSettingsConfig, expected_call
161
+ ):
162
+ """We expect the right call for each setting."""
163
+
164
+ # Arrange.
165
+ simulator = mock.create_autospec(base_simulator.BaseSimulator)
166
+ adb_controller = simulator.create_adb_controller.return_value
167
+ adb_controller.execute_command.return_value = b''
168
+ device_settings = device_settings_lib.DeviceSettings(simulator)
169
+
170
+ # Act.
171
+ device_settings.update(settings)
172
+
173
+ # Assert.
174
+ adb_controller.execute_command.assert_has_calls(
175
+ [expected_call], any_order=True
176
+ )
177
+
178
+ def test_get_orientation_bad_response(self):
179
+ """The orientation should be unset if the underlying response is bad."""
180
+
181
+ # Arrange.
182
+ simulator = mock.create_autospec(base_simulator.BaseSimulator)
183
+ adb_controller = simulator.create_adb_controller.return_value
184
+ adb_controller.execute_command.return_value = b''
185
+ device_settings = device_settings_lib.DeviceSettings(simulator)
186
+
187
+ # Act.
188
+ orientation = device_settings.get_orientation()
189
+
190
+ # Assert.
191
+ np.testing.assert_array_equal(orientation, np.zeros(4))
192
+
193
+ def test_get_orientation_bad_orientation(self):
194
+ """The orientation should be unset if the underlying orientation is bad."""
195
+
196
+ # Arrange.
197
+ simulator = mock.create_autospec(base_simulator.BaseSimulator)
198
+ adb_controller = simulator.create_adb_controller.return_value
199
+ adb_controller.execute_command.return_value = b' InputDeviceOrientation: 9'
200
+ device_settings = device_settings_lib.DeviceSettings(simulator)
201
+
202
+ # Act.
203
+ orientation = device_settings.get_orientation()
204
+
205
+ # Assert.
206
+ np.testing.assert_array_equal(orientation, np.zeros(4))
207
+
208
+ def test_get_orientation_success(self):
209
+ """Checks that the orientation comes back as expected."""
210
+
211
+ # Arrange.
212
+ simulator = mock.create_autospec(base_simulator.BaseSimulator)
213
+ adb_controller = simulator.create_adb_controller.return_value
214
+ adb_controller.execute_command.return_value = b' InputDeviceOrientation: 3'
215
+ device_settings = device_settings_lib.DeviceSettings(simulator)
216
+
217
+ # Act.
218
+ orientation = device_settings.get_orientation()
219
+ # The output should be idempotent if the underlying system did not change.
220
+ orientation_again = device_settings.get_orientation()
221
+
222
+ # Assert.
223
+ np.testing.assert_array_equal(orientation, np.array([0, 0, 0, 1]))
224
+ np.testing.assert_array_equal(orientation, orientation_again)
225
+
226
+
227
+ if __name__ == '__main__':
228
+ absltest.main()
@@ -1,5 +1,5 @@
1
1
  # coding=utf-8
2
- # Copyright 2022 DeepMind Technologies Limited.
2
+ # Copyright 2024 DeepMind Technologies Limited.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
16
16
  """A ThreadFunction that runs and parses adb dumpsys."""
17
17
 
18
18
  import concurrent.futures
19
- from typing import Optional
20
19
 
21
20
  from absl import logging
22
21
  from android_env.components import app_screen_checker as app_screen_checker_lib
@@ -56,9 +55,9 @@ class DumpsysThread:
56
55
  self._check_frequency = check_frequency
57
56
  self._max_failed_activity_extraction = max_failed_current_activity
58
57
  self._num_failed_activity_extraction = 0
59
- self._latest_check: Optional[concurrent.futures.Future] = None
58
+ self._latest_check: concurrent.futures.Future | None = None
60
59
 
61
- def check_user_exited(self, timeout: Optional[float] = None) -> bool:
60
+ def check_user_exited(self, timeout: float | None = None) -> bool:
62
61
  """Returns True if the user is not in the expected screen.
63
62
 
64
63
  Args:
@@ -1,5 +1,5 @@
1
1
  # coding=utf-8
2
- # Copyright 2022 DeepMind Technologies Limited.
2
+ # Copyright 2024 DeepMind Technologies Limited.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  # coding=utf-8
2
- # Copyright 2022 DeepMind Technologies Limited.
2
+ # Copyright 2024 DeepMind Technologies Limited.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -16,41 +16,83 @@
16
16
  """Definitions of exceptions used by AndroidEnv."""
17
17
 
18
18
 
19
- class ReadObservationError(Exception):
19
+ class AndroidEnvError(Exception):
20
+ """Base class for all known errors generated by AndroidEnv."""
21
+
22
+ # An integer that identifies this class of error.
23
+ # Subclasses should use a different value.
24
+ ERROR_CODE: int = 0
25
+
26
+
27
+ class ReadObservationError(AndroidEnvError):
20
28
  """When the environment is unable to obtain an observation from a simulator."""
21
29
 
30
+ ERROR_CODE = 1
31
+
22
32
 
23
- class CoordinatorError(Exception):
33
+ class CoordinatorError(AndroidEnvError):
24
34
  """Error raised by the Coordinator."""
25
35
 
36
+ ERROR_CODE = 2
37
+
26
38
 
27
39
  class TooManyRestartsError(CoordinatorError):
28
40
  """The number of restarts has exceeded _MAX_RESTART_TRIES."""
29
41
 
42
+ ERROR_CODE = 3
30
43
 
31
- class AdbControllerError(Exception):
32
- """Errors that can be raised by ADBController."""
33
44
 
45
+ class AdbControllerError(AndroidEnvError):
46
+ """Errors that can be raised by ADBController."""
34
47
 
35
- class AdbControllerDeviceTimeoutError(AdbControllerError):
36
- """Raised when a device takes too long to respond."""
48
+ ERROR_CODE = 4
37
49
 
38
50
 
39
- class SimulatorError(Exception):
51
+ class SimulatorError(AndroidEnvError):
40
52
  """Errors that can be raised by a simulator."""
41
53
 
54
+ ERROR_CODE = 5
55
+
42
56
 
43
- class SendActionError(Exception):
57
+ class SendActionError(AndroidEnvError):
44
58
  """Raised when action couldn't be sent successfully."""
45
59
 
60
+ ERROR_CODE = 6
46
61
 
47
- class StepCommandError(Exception):
62
+
63
+ class StepCommandError(AndroidEnvError):
48
64
  """Raised when setup step interpreter cannot process a command."""
49
65
 
66
+ ERROR_CODE = 7
67
+
50
68
 
51
69
  class WaitForAppScreenError(StepCommandError):
52
70
  """Raised when the wait_for_app_screen success check is not met."""
53
71
 
72
+ ERROR_CODE = 8
73
+
54
74
 
55
75
  class CheckInstallError(StepCommandError):
56
76
  """Raised when the check_install success check is not met."""
77
+
78
+ ERROR_CODE = 9
79
+
80
+
81
+ def from_code(code: int, msg: str = '') -> AndroidEnvError | None:
82
+ """Returns an AndroidEnvError instance from the given arguments."""
83
+
84
+ code_to_error = {
85
+ 0: AndroidEnvError,
86
+ 1: ReadObservationError,
87
+ 2: CoordinatorError,
88
+ 3: TooManyRestartsError,
89
+ 4: AdbControllerError,
90
+ 5: SimulatorError,
91
+ 6: SendActionError,
92
+ 7: StepCommandError,
93
+ 8: WaitForAppScreenError,
94
+ 9: CheckInstallError,
95
+ }
96
+
97
+ if code in code_to_error:
98
+ return code_to_error[code](msg)
@@ -0,0 +1,110 @@
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
+ """Tests for errors.py."""
17
+
18
+ from absl.testing import absltest
19
+ from absl.testing import parameterized
20
+ from android_env.components import errors
21
+
22
+
23
+ class ErrorsTest(parameterized.TestCase):
24
+
25
+ @parameterized.parameters(
26
+ (errors.ReadObservationError, 1),
27
+ (errors.CoordinatorError, 2),
28
+ (errors.TooManyRestartsError, 3),
29
+ (errors.AdbControllerError, 4),
30
+ (errors.SimulatorError, 5),
31
+ (errors.SendActionError, 6),
32
+ (errors.StepCommandError, 7),
33
+ (errors.WaitForAppScreenError, 8),
34
+ (errors.CheckInstallError, 9),
35
+ )
36
+ def test_error_codes(self, error, expected_error_code):
37
+ with self.assertRaises(error) as context:
38
+ raise error()
39
+ self.assertEqual(context.exception.ERROR_CODE, expected_error_code)
40
+
41
+ def test_error_codes_unique(self):
42
+ error_codes = set()
43
+ errors_list = (
44
+ errors.ReadObservationError,
45
+ errors.CoordinatorError,
46
+ errors.TooManyRestartsError,
47
+ errors.AdbControllerError,
48
+ errors.SimulatorError,
49
+ errors.SendActionError,
50
+ errors.StepCommandError,
51
+ errors.WaitForAppScreenError,
52
+ errors.CheckInstallError,
53
+ )
54
+ for error in errors_list:
55
+ self.assertNotIn(error.ERROR_CODE, error_codes)
56
+ error_codes.add(error.ERROR_CODE)
57
+
58
+ @parameterized.parameters([
59
+ errors.ReadObservationError(),
60
+ errors.CoordinatorError(),
61
+ errors.TooManyRestartsError(),
62
+ errors.AdbControllerError(),
63
+ errors.SimulatorError(),
64
+ errors.SendActionError(),
65
+ errors.StepCommandError(),
66
+ errors.WaitForAppScreenError(),
67
+ errors.CheckInstallError(),
68
+ ])
69
+ def test_all_errors_are_androidenv_errors(self, error):
70
+ self.assertIsInstance(error, errors.AndroidEnvError)
71
+
72
+ @parameterized.named_parameters([
73
+ ('less_than_zero', -1),
74
+ # The largest `ERROR_CODE` is currently `CheckInstallError == 10`.
75
+ ('greater_than_all_errors', 10 + 1),
76
+ ('less_than_zero_float', -3.14159265),
77
+ ('greater_than_all_errors_float', 123.456),
78
+ ])
79
+ def test_from_code_unsupported_code(self, code: int):
80
+ """Unsupported errors should raise `RuntimeError`."""
81
+
82
+ self.assertIsNone(errors.from_code(code))
83
+
84
+ @parameterized.parameters([
85
+ (-1, None, 'No such error code.'),
86
+ (0, errors.AndroidEnvError, 'hello'),
87
+ (0, errors.AndroidEnvError, ''),
88
+ (1, errors.ReadObservationError, 'Could not read obs.'),
89
+ (2, errors.CoordinatorError, 'Some error'),
90
+ (3, errors.TooManyRestartsError, 'Too many already...'),
91
+ (4, errors.AdbControllerError, 'Some adb error...'),
92
+ (5, errors.SimulatorError, 'Simulator is not coping.'),
93
+ (6, errors.SendActionError, 'Could not send action.'),
94
+ (7, errors.StepCommandError, 'Some issue setting up the task.'),
95
+ (8, errors.WaitForAppScreenError, 'Waited for too long!'),
96
+ (9, errors.CheckInstallError, 'App did not install correctly.'),
97
+ ])
98
+ def test_from_code(self, code: int, expected_class: errors.AndroidEnvError,
99
+ msg: str):
100
+ """`from_code` should produce consistent outputs for known errors."""
101
+
102
+ error = errors.from_code(code, msg)
103
+ if error is not None:
104
+ self.assertIsInstance(error, expected_class)
105
+ self.assertEqual(error.ERROR_CODE, code)
106
+ self.assertEqual(str(error), msg)
107
+
108
+
109
+ if __name__ == '__main__':
110
+ absltest.main()
@@ -1,5 +1,5 @@
1
1
  # coding=utf-8
2
- # Copyright 2022 DeepMind Technologies Limited.
2
+ # Copyright 2024 DeepMind Technologies Limited.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -16,8 +16,8 @@
16
16
  """Abstract class for handling a stream of logs from a simulator."""
17
17
 
18
18
  import abc
19
+ from collections.abc import Generator, Sequence
19
20
  import threading
20
- from typing import Generator, List
21
21
  from absl import logging
22
22
 
23
23
 
@@ -44,8 +44,10 @@ class LogStream(metaclass=abc.ABCMeta):
44
44
 
45
45
  @abc.abstractmethod
46
46
  def stop_stream(self) -> None:
47
- """Terminates log stream process from the simulator."""
48
- pass
47
+ """Terminates the log stream.
48
+
49
+ NOTE: This should only be called _after_ `get_stream_output()`.
50
+ """
49
51
 
50
52
  def pause_stream(self) -> None:
51
53
  """No lines are yielded while the event is not set."""
@@ -57,6 +59,6 @@ class LogStream(metaclass=abc.ABCMeta):
57
59
  logging.info('Resuming LogStream.')
58
60
  self._should_stream.set()
59
61
 
60
- def set_log_filters(self, log_filters: List[str]):
62
+ def set_log_filters(self, log_filters: Sequence[str]):
61
63
  """Sets the filters for the log stream."""
62
64
  self._filters = list(log_filters) + ['*:S']
@@ -1,5 +1,5 @@
1
1
  # coding=utf-8
2
- # Copyright 2022 DeepMind Technologies Limited.
2
+ # Copyright 2024 DeepMind Technologies Limited.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  # coding=utf-8
2
- # Copyright 2022 DeepMind Technologies Limited.
2
+ # Copyright 2024 DeepMind Technologies Limited.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -15,20 +15,21 @@
15
15
 
16
16
  """A class that launches a thread to read Android log outputs."""
17
17
 
18
+ from collections.abc import Callable
19
+ import dataclasses
18
20
  import re
19
21
  import threading
20
- # `typing.Pattern` has been deprecated in Python 3.9 in favor of `re.Pattern`,
21
- # but it is not available even in slightly older Python versions.
22
- # Please see https://www.python.org/dev/peps/pep-0585/
23
- from typing import Callable, Match, NamedTuple, Pattern
24
22
 
25
23
  from absl import logging
26
24
  from android_env.components import log_stream as log_stream_lib
27
25
 
28
26
 
29
- class EventListener(NamedTuple):
30
- regexp: Pattern[str]
31
- handler_fn: Callable[[Pattern[str], Match[str]], None]
27
+ @dataclasses.dataclass
28
+ class EventListener:
29
+ """A function that's called when an event is triggered."""
30
+
31
+ regexp: re.Pattern[str]
32
+ handler_fn: Callable[[re.Pattern[str], re.Match[str]], None]
32
33
 
33
34
 
34
35
  class LogcatThread:
@@ -1,5 +1,5 @@
1
1
  # coding=utf-8
2
- # Copyright 2022 DeepMind Technologies Limited.
2
+ # Copyright 2024 DeepMind Technologies Limited.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
17
17
 
18
18
  import re
19
19
  import threading
20
- from typing import Match, Pattern
21
20
 
22
21
  from absl.testing import absltest
23
22
  from android_env.components import log_stream
@@ -25,7 +24,7 @@ from android_env.components import logcat_thread
25
24
  from android_env.proto import task_pb2
26
25
 
27
26
 
28
- class FakeStream():
27
+ class FakeStream:
29
28
  """This class simulates the logs coming from ADB."""
30
29
 
31
30
  def __init__(self):
@@ -108,7 +107,7 @@ class LogcatThreadTest(absltest.TestCase):
108
107
  # Set up a listener that modifies an arbitrary state.
109
108
  some_state = threading.Event()
110
109
 
111
- def my_handler(event: Pattern[str], match: Match[str]):
110
+ def my_handler(event: re.Pattern[str], match: re.Match[str]):
112
111
  del event, match
113
112
  nonlocal some_state
114
113
  some_state.set()
@@ -1,5 +1,5 @@
1
1
  # coding=utf-8
2
- # Copyright 2022 DeepMind Technologies Limited.
2
+ # Copyright 2024 DeepMind Technologies Limited.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
15
15
 
16
16
  """Utils for AndroidEnv."""
17
17
 
18
- from typing import Sequence, Tuple
18
+ from collections.abc import Sequence
19
19
 
20
20
  from dm_env import specs
21
21
  import numpy as np
@@ -24,7 +24,7 @@ import numpy as np
24
24
  def touch_position_to_pixel_position(
25
25
  touch_position: np.ndarray,
26
26
  width_height: Sequence[int],
27
- ) -> Tuple[int, int]:
27
+ ) -> tuple[int, int]:
28
28
  """Maps touch position in [0,1] to the corresponding pixel on the screen."""
29
29
  touch_pixels = (touch_position * width_height).astype(np.int32)
30
30
  cap_idx = lambda v, idx_len: min(v, idx_len - 1)
@@ -38,27 +38,27 @@ def transpose_pixels(frame: np.ndarray) -> np.ndarray:
38
38
 
39
39
  def orient_pixels(frame: np.ndarray, orientation: int) -> np.ndarray:
40
40
  """Rotates screen pixels according to the given orientation."""
41
- if orientation == 0: # PORTRAIT_90
42
- return frame
43
- elif orientation == 1: # LANDSCAPE_90
44
- return np.rot90(frame, k=3, axes=(0, 1))
45
- elif orientation == 2: # PORTRAIT_180
46
- return np.rot90(frame, k=2, axes=(0, 1))
47
- elif orientation == 3: # LANDSCAPE_270
48
- return np.rot90(frame, k=1, axes=(0, 1))
49
- else:
50
- raise ValueError(
51
- 'Orientation must be an integer in [0, 3] but is %r' % orientation)
41
+
42
+ match orientation:
43
+ case 0: # PORTRAIT_90
44
+ return frame
45
+ case 1: # LANDSCAPE_90
46
+ return np.rot90(frame, k=3, axes=(0, 1))
47
+ case 2: # PORTRAIT_180
48
+ return np.rot90(frame, k=2, axes=(0, 1))
49
+ case 3: # LANDSCAPE_270
50
+ return np.rot90(frame, k=1, axes=(0, 1))
51
+ case _:
52
+ raise ValueError(
53
+ 'Orientation must be an integer in [0, 3] but is %r' % orientation
54
+ )
52
55
 
53
56
 
54
- def convert_int_to_float(data: np.ndarray,
55
- data_spec: specs.Array,
56
- float_type: np.dtype = np.float32):
57
+ def convert_int_to_float(data: np.ndarray, data_spec: specs.Array):
57
58
  """Converts an array of int values to floats between 0 and 1."""
59
+
58
60
  if not np.issubdtype(data.dtype, np.integer):
59
61
  raise TypeError(f'{data.dtype} is not an integer type')
60
- if not np.issubdtype(float_type, np.floating):
61
- raise TypeError(f'{float_type} is not a floating-point type')
62
62
  if isinstance(data_spec, specs.BoundedArray):
63
63
  value_min = data_spec.minimum
64
64
  value_max = data_spec.maximum
@@ -67,4 +67,4 @@ def convert_int_to_float(data: np.ndarray,
67
67
  iinfo = np.iinfo(data_spec.dtype)
68
68
  value_min = iinfo.min
69
69
  value_max = iinfo.max
70
- return float_type(1.0 * (data - value_min) / (value_max - value_min))
70
+ return np.float32(1.0 * (data - value_min) / (value_max - value_min))