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.
|
@@ -17,42 +17,73 @@
|
|
17
17
|
|
18
18
|
import os
|
19
19
|
import time
|
20
|
-
from typing import Any
|
20
|
+
from typing import Any
|
21
21
|
|
22
22
|
from absl import logging
|
23
23
|
from android_env.components import adb_controller
|
24
24
|
from android_env.components import adb_log_stream
|
25
|
+
from android_env.components import config_classes
|
25
26
|
from android_env.components import errors
|
26
27
|
from android_env.components import log_stream
|
27
28
|
from android_env.components.simulators import base_simulator
|
28
29
|
from android_env.components.simulators.emulator import emulator_launcher
|
30
|
+
from android_env.proto import state_pb2
|
29
31
|
import grpc
|
30
32
|
import numpy as np
|
31
33
|
import portpicker
|
32
34
|
|
33
35
|
from android_env.proto import emulator_controller_pb2
|
34
36
|
from android_env.proto import emulator_controller_pb2_grpc
|
37
|
+
from android_env.proto import snapshot_service_pb2
|
38
|
+
from android_env.proto import snapshot_service_pb2_grpc
|
35
39
|
from google.protobuf import empty_pb2
|
36
40
|
|
37
41
|
|
38
|
-
|
42
|
+
_DEFAULT_SNAPSHOT_NAME: str = 'default_snapshot'
|
43
|
+
|
44
|
+
|
45
|
+
def _is_existing_emulator_provided(
|
46
|
+
launcher_config: config_classes.EmulatorLauncherConfig,
|
47
|
+
) -> bool:
|
39
48
|
"""Returns true if all necessary args were provided."""
|
40
|
-
return bool(launcher_args.get('adb_port') and
|
41
|
-
launcher_args.get('emulator_console_port') and
|
42
|
-
launcher_args.get('grpc_port'))
|
43
49
|
|
50
|
+
return bool(
|
51
|
+
launcher_config.adb_port
|
52
|
+
and launcher_config.emulator_console_port
|
53
|
+
and launcher_config.grpc_port
|
54
|
+
)
|
44
55
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
+
|
57
|
+
def _pick_adb_port() -> int:
|
58
|
+
"""Tries to pick a port in the recommended range 5555-5585.
|
59
|
+
|
60
|
+
If no such port can be found, will return a random unused port. More info:
|
61
|
+
https://developer.android.com/studio/command-line/adb#howadbworks.
|
62
|
+
|
63
|
+
Returns:
|
64
|
+
port: an available port for adb.
|
65
|
+
"""
|
66
|
+
|
67
|
+
for p in range(5555, 5587, 2):
|
68
|
+
if portpicker.is_port_free(p):
|
69
|
+
return p
|
70
|
+
return portpicker.pick_unused_port()
|
71
|
+
|
72
|
+
|
73
|
+
def _pick_emulator_grpc_port() -> int:
|
74
|
+
"""Tries to pick the recommended port for grpc.
|
75
|
+
|
76
|
+
If no such port can be found, will return a random unused port. More info:
|
77
|
+
https://android.googlesource.com/platform/external/qemu/+/emu-master-dev/android/android-grpc/docs/.
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
port: an available port for emulator grpc.
|
81
|
+
"""
|
82
|
+
|
83
|
+
if portpicker.is_port_free(8554):
|
84
|
+
return 8554
|
85
|
+
else:
|
86
|
+
return portpicker.pick_unused_port()
|
56
87
|
|
57
88
|
|
58
89
|
class EmulatorBootError(errors.SimulatorError):
|
@@ -66,69 +97,86 @@ class EmulatorCrashError(errors.SimulatorError):
|
|
66
97
|
class EmulatorSimulator(base_simulator.BaseSimulator):
|
67
98
|
"""Controls an Android Emulator."""
|
68
99
|
|
69
|
-
def __init__(self,
|
70
|
-
|
71
|
-
adb_controller_args: Dict[str, Any],
|
72
|
-
tmp_dir: str = '/tmp/android_env/simulator',
|
73
|
-
logfile_path: Optional[str] = None,
|
74
|
-
**kwargs):
|
75
|
-
"""Instantiates an EmulatorSimulator.
|
100
|
+
def __init__(self, config: config_classes.EmulatorConfig):
|
101
|
+
"""Instantiates an EmulatorSimulator."""
|
76
102
|
|
77
|
-
|
78
|
-
|
79
|
-
adb_controller_args: Arguments for AdbController.
|
80
|
-
tmp_dir: Temporary directory to hold simulator files.
|
81
|
-
logfile_path: Path to file which holds emulator logs. If not provided,
|
82
|
-
it will be determined by the EmulatorLauncher.
|
83
|
-
**kwargs: keyword arguments for base class.
|
84
|
-
"""
|
103
|
+
super().__init__(config)
|
104
|
+
self._config = config
|
85
105
|
|
86
106
|
# If adb_port, console_port and grpc_port are all already provided,
|
87
107
|
# we assume the emulator already exists and there's no need to launch.
|
88
|
-
if
|
108
|
+
if _is_existing_emulator_provided(self._config.emulator_launcher):
|
89
109
|
self._existing_emulator_provided = True
|
90
|
-
|
91
|
-
|
92
|
-
self._grpc_port = emulator_launcher_args['grpc_port']
|
93
|
-
logging.info('Connecting to existing emulator "%r"', self._adb_port)
|
110
|
+
logging.info('Connecting to existing emulator "%r"',
|
111
|
+
self.adb_device_name())
|
94
112
|
else:
|
95
113
|
self._existing_emulator_provided = False
|
96
|
-
self.
|
97
|
-
self.
|
98
|
-
|
114
|
+
self._config.emulator_launcher.adb_port = _pick_adb_port()
|
115
|
+
self._config.emulator_launcher.emulator_console_port = (
|
116
|
+
portpicker.pick_unused_port()
|
117
|
+
)
|
118
|
+
self._config.emulator_launcher.grpc_port = _pick_emulator_grpc_port()
|
99
119
|
|
100
120
|
self._channel = None
|
101
|
-
self._emulator_stub =
|
121
|
+
self._emulator_stub: emulator_controller_pb2_grpc.EmulatorControllerStub | None = (
|
122
|
+
None
|
123
|
+
)
|
124
|
+
self._snapshot_stub = None
|
102
125
|
# Set the image format to RGBA. The width and height of the returned
|
103
126
|
# screenshots will use the device's width and height.
|
104
127
|
self._image_format = emulator_controller_pb2.ImageFormat(
|
105
128
|
format=emulator_controller_pb2.ImageFormat.ImgFormat.RGBA8888)
|
106
129
|
|
107
|
-
|
130
|
+
if (
|
131
|
+
self._config.launch_n_times_without_reboot
|
132
|
+
> self._config.launch_n_times_without_reinstall
|
133
|
+
):
|
134
|
+
raise ValueError(
|
135
|
+
'Number of launch attempts before reboot'
|
136
|
+
f' ({self._config.launch_n_times_without_reboot}) should not be'
|
137
|
+
' greater than number of launch attempts before reinstall'
|
138
|
+
f' ({self._config.launch_n_times_without_reinstall})'
|
139
|
+
)
|
108
140
|
|
109
141
|
# Initialize own ADB controller.
|
110
|
-
self.
|
142
|
+
self._config.adb_controller.device_name = self.adb_device_name()
|
111
143
|
self._adb_controller = self.create_adb_controller()
|
112
144
|
self._adb_controller.init_server()
|
113
|
-
logging.info(
|
114
|
-
|
145
|
+
logging.info(
|
146
|
+
'Initialized simulator with ADB server port %r.',
|
147
|
+
self._config.adb_controller.adb_server_port,
|
148
|
+
)
|
115
149
|
|
116
150
|
# If necessary, create EmulatorLauncher.
|
117
151
|
if self._existing_emulator_provided:
|
118
|
-
self._logfile_path = logfile_path or None
|
152
|
+
self._logfile_path = self._config.logfile_path or None
|
119
153
|
self._launcher = None
|
120
154
|
else:
|
121
|
-
|
122
|
-
'
|
123
|
-
|
124
|
-
'emulator_console_port': self._console_port,
|
125
|
-
'grpc_port': self._grpc_port,
|
126
|
-
'tmp_dir': tmp_dir,
|
127
|
-
})
|
128
|
-
logging.info('emulator_launcher_args: %r', emulator_launcher_args)
|
155
|
+
logging.info(
|
156
|
+
'emulator_launcher config: %r', self._config.emulator_launcher
|
157
|
+
)
|
129
158
|
self._launcher = emulator_launcher.EmulatorLauncher(
|
130
|
-
|
131
|
-
|
159
|
+
config=self._config.emulator_launcher,
|
160
|
+
adb_controller_config=self._config.adb_controller,
|
161
|
+
)
|
162
|
+
self._logfile_path = (
|
163
|
+
self._config.logfile_path or self._launcher.logfile_path()
|
164
|
+
)
|
165
|
+
|
166
|
+
def _reconnect_on_grpc_error(func):
|
167
|
+
"""Decorator function for reconnecting to emulator upon grpc errors."""
|
168
|
+
|
169
|
+
def wrapper(self, *args, **kwargs):
|
170
|
+
try:
|
171
|
+
return func(self, *args, **kwargs)
|
172
|
+
except grpc.RpcError:
|
173
|
+
logging.exception('RpcError caught. Reconnecting to emulator...')
|
174
|
+
self._emulator_stub, self._snapshot_stub = self._connect_to_emulator(
|
175
|
+
self._config.emulator_launcher.grpc_port
|
176
|
+
)
|
177
|
+
return func(self, *args, **kwargs)
|
178
|
+
|
179
|
+
return wrapper
|
132
180
|
|
133
181
|
def get_logs(self) -> str:
|
134
182
|
"""Returns logs recorded by the emulator."""
|
@@ -139,51 +187,155 @@ class EmulatorSimulator(base_simulator.BaseSimulator):
|
|
139
187
|
return f'Logfile does not exist: {self._logfile_path}.'
|
140
188
|
|
141
189
|
def adb_device_name(self) -> str:
|
142
|
-
return 'emulator-%s' % (self.
|
190
|
+
return 'emulator-%s' % (self._config.emulator_launcher.adb_port - 1)
|
143
191
|
|
144
192
|
def create_adb_controller(self):
|
145
193
|
"""Returns an ADB controller which can communicate with this simulator."""
|
146
|
-
return adb_controller.AdbController(
|
147
|
-
device_name=self.adb_device_name(),
|
148
|
-
**self._adb_controller_args)
|
194
|
+
return adb_controller.AdbController(self._config.adb_controller)
|
149
195
|
|
150
196
|
def create_log_stream(self) -> log_stream.LogStream:
|
151
197
|
return adb_log_stream.AdbLogStream(
|
152
198
|
adb_command_prefix=self._adb_controller.command_prefix(),
|
153
|
-
verbose=self.
|
154
|
-
|
155
|
-
def _restart_impl(self) -> None:
|
156
|
-
if self._launcher is not None:
|
157
|
-
logging.info('Restarting the emulator...')
|
158
|
-
self._shutdown_emulator()
|
159
|
-
self._launcher.launch_emulator_process()
|
160
|
-
self._emulator_stub = self._connect_to_emulator(self._grpc_port)
|
161
|
-
self._confirm_booted()
|
162
|
-
logging.info('Done restarting the emulator.')
|
199
|
+
verbose=self._config.verbose_logs,
|
200
|
+
)
|
163
201
|
|
164
202
|
def _launch_impl(self) -> None:
|
165
|
-
"""
|
203
|
+
"""Prepares an Android Emulator for RL interaction.
|
204
|
+
|
205
|
+
The behavior depends on `self._num_launch_attempts`'s value:
|
206
|
+
* <= self._config.launch_n_times_without_reboot -> Normal boot behavior.
|
207
|
+
* > self._config.launch_n_times_without_reboot but <=
|
208
|
+
self._config.launch_n_times_without_reinstall -> reboot (i.e. process
|
209
|
+
is killed and started again).
|
210
|
+
* > self._config.launch_n_times_without_reinstall -> reinstall (i.e.
|
211
|
+
process is killed, emulator files are deleted and the process started
|
212
|
+
again).
|
213
|
+
"""
|
214
|
+
|
215
|
+
logging.info('Attempt %r at launching the Android Emulator (%r)',
|
216
|
+
self._num_launch_attempts, self.adb_device_name())
|
166
217
|
|
167
|
-
# If required, launch an emulator process.
|
168
218
|
if self._launcher is not None:
|
219
|
+
# If not the first time, then shutdown the emulator first.
|
220
|
+
if (
|
221
|
+
self._emulator_stub is not None
|
222
|
+
and self._num_launch_attempts
|
223
|
+
> self._config.launch_n_times_without_reboot
|
224
|
+
):
|
225
|
+
self._shutdown_emulator()
|
226
|
+
# Subsequent attempts cause the emulator files to be reinstalled.
|
227
|
+
if (
|
228
|
+
self._num_launch_attempts
|
229
|
+
> self._config.launch_n_times_without_reinstall
|
230
|
+
):
|
231
|
+
logging.info('Closing emulator (%r)', self.adb_device_name())
|
232
|
+
self._launcher.close()
|
233
|
+
self._launcher = emulator_launcher.EmulatorLauncher(
|
234
|
+
config=self._config.emulator_launcher,
|
235
|
+
adb_controller_config=self._config.adb_controller,
|
236
|
+
)
|
169
237
|
self._launcher.launch_emulator_process()
|
170
|
-
|
171
238
|
# Establish grpc connection to emulator process.
|
172
|
-
self._emulator_stub = self._connect_to_emulator(
|
239
|
+
self._emulator_stub, self._snapshot_stub = self._connect_to_emulator(
|
240
|
+
self._config.emulator_launcher.grpc_port
|
241
|
+
)
|
173
242
|
|
174
243
|
# Confirm booted status.
|
175
244
|
try:
|
176
245
|
self._confirm_booted()
|
177
246
|
except EmulatorCrashError:
|
178
|
-
logging.exception(
|
179
|
-
|
180
|
-
|
247
|
+
logging.exception('Failed to confirm booted status of emulator.')
|
248
|
+
|
249
|
+
logging.info('Done booting the Android Emulator.')
|
250
|
+
|
251
|
+
def load_state(
|
252
|
+
self, request: state_pb2.LoadStateRequest
|
253
|
+
) -> state_pb2.LoadStateResponse:
|
254
|
+
"""Loads a state using the emulator's snapshotting mechanism.
|
255
|
+
|
256
|
+
Args:
|
257
|
+
request: The `LoadStateRequest`. In this case, `args` should be a dict
|
258
|
+
containing the key 'snapshot_name', representing the name of the
|
259
|
+
snapshot to load. If `request.args.snapshot_name` is `None`, a default
|
260
|
+
snapshot name is used.
|
261
|
+
|
262
|
+
Returns:
|
263
|
+
A response indicating whether the snapshot was successfully loaded.
|
264
|
+
* If the snapshot was loaded successfully, the status will be `OK`.
|
265
|
+
* If no snapshot of the given name was found, the status will be
|
266
|
+
`NOT_FOUND`.
|
267
|
+
* If an error occurred during the snapshot loading process, the status
|
268
|
+
will be `ERROR` and the `error_message` field will be filled.
|
269
|
+
"""
|
270
|
+
assert self._snapshot_stub is not None
|
271
|
+
snapshot_name = request.args.get('snapshot_name', _DEFAULT_SNAPSHOT_NAME)
|
272
|
+
snapshot_list = self._snapshot_stub.ListSnapshots(
|
273
|
+
snapshot_service_pb2.SnapshotFilter(
|
274
|
+
statusFilter=snapshot_service_pb2.SnapshotFilter.LoadStatus.All
|
275
|
+
)
|
276
|
+
)
|
277
|
+
if any(
|
278
|
+
snapshot.snapshot_id == snapshot_name
|
279
|
+
for snapshot in snapshot_list.snapshots
|
280
|
+
):
|
281
|
+
snapshot_result = self._snapshot_stub.LoadSnapshot(
|
282
|
+
snapshot_service_pb2.SnapshotPackage(snapshot_id=snapshot_name)
|
283
|
+
)
|
284
|
+
if snapshot_result.success:
|
285
|
+
return state_pb2.LoadStateResponse(
|
286
|
+
status=state_pb2.LoadStateResponse.Status.OK
|
287
|
+
)
|
288
|
+
else:
|
289
|
+
return state_pb2.LoadStateResponse(
|
290
|
+
status=state_pb2.LoadStateResponse.Status.ERROR,
|
291
|
+
error_message=snapshot_result.err.decode('utf-8'),
|
292
|
+
)
|
293
|
+
|
294
|
+
else:
|
295
|
+
return state_pb2.LoadStateResponse(
|
296
|
+
status=state_pb2.LoadStateResponse.Status.NOT_FOUND
|
297
|
+
)
|
298
|
+
|
299
|
+
def save_state(
|
300
|
+
self, request: state_pb2.SaveStateRequest
|
301
|
+
) -> state_pb2.SaveStateResponse:
|
302
|
+
"""Saves a state using the emulator's snapshotting mechanism.
|
303
|
+
|
304
|
+
Args:
|
305
|
+
request: The `SaveStateRequest`. In this case, `args` should be a dict
|
306
|
+
containing the key 'snapshot_name', representing the name of the
|
307
|
+
snapshot to save. If `request.args.snapshot_name` is `None`, a default
|
308
|
+
snapshot name is used.
|
309
|
+
|
310
|
+
Returns:
|
311
|
+
A response indicating whether the snapshot was successfully saved.
|
312
|
+
* If the snapshot was saved successfully, the status will be `OK`.
|
313
|
+
* If an error occurred during the snapshot saving process, the status
|
314
|
+
will be `ERROR` and the `error_message` field will be filled.
|
315
|
+
"""
|
316
|
+
assert self._snapshot_stub is not None
|
317
|
+
snapshot_name = request.args.get('snapshot_name', _DEFAULT_SNAPSHOT_NAME)
|
318
|
+
snapshot_result = self._snapshot_stub.SaveSnapshot(
|
319
|
+
snapshot_service_pb2.SnapshotPackage(snapshot_id=snapshot_name)
|
320
|
+
)
|
321
|
+
if snapshot_result.success:
|
322
|
+
return state_pb2.SaveStateResponse(
|
323
|
+
status=state_pb2.SaveStateResponse.Status.OK
|
324
|
+
)
|
325
|
+
else:
|
326
|
+
return state_pb2.SaveStateResponse(
|
327
|
+
status=state_pb2.SaveStateResponse.Status.ERROR,
|
328
|
+
error_message=snapshot_result.err.decode('utf-8'),
|
329
|
+
)
|
181
330
|
|
182
331
|
def _connect_to_emulator(
|
183
332
|
self,
|
184
333
|
grpc_port: int,
|
185
|
-
timeout_sec: int =
|
186
|
-
) ->
|
334
|
+
timeout_sec: int = 100,
|
335
|
+
) -> tuple[
|
336
|
+
emulator_controller_pb2_grpc.EmulatorControllerStub,
|
337
|
+
snapshot_service_pb2_grpc.SnapshotServiceStub,
|
338
|
+
]:
|
187
339
|
"""Connects to an emulator and returns a corresponsing stub."""
|
188
340
|
|
189
341
|
logging.info('Creating gRPC channel to the emulator on port %r', grpc_port)
|
@@ -201,18 +353,26 @@ class EmulatorSimulator(base_simulator.BaseSimulator):
|
|
201
353
|
'Failed to connect to the emulator.') from grpc_error
|
202
354
|
|
203
355
|
logging.info('Added gRPC channel for the Emulator on port %s', port)
|
204
|
-
|
356
|
+
emulator_controller_stub = (
|
357
|
+
emulator_controller_pb2_grpc.EmulatorControllerStub(self._channel)
|
358
|
+
)
|
359
|
+
snapshot_stub = snapshot_service_pb2_grpc.SnapshotServiceStub(self._channel)
|
360
|
+
return emulator_controller_stub, snapshot_stub
|
205
361
|
|
206
362
|
@_reconnect_on_grpc_error
|
207
363
|
def _confirm_booted(self, startup_wait_time_sec: int = 300):
|
208
364
|
"""Waits until the emulator is fully booted."""
|
209
365
|
|
366
|
+
assert (
|
367
|
+
self._emulator_stub is not None
|
368
|
+
), 'Emulator stub has not been initialized yet.'
|
210
369
|
start_time = time.time()
|
211
370
|
deadline = start_time + startup_wait_time_sec
|
212
371
|
success = False
|
213
372
|
while time.time() < deadline:
|
214
373
|
emu_status = self._emulator_stub.getStatus(empty_pb2.Empty())
|
215
|
-
logging.info('Waiting for emulator to start... (%rms)',
|
374
|
+
logging.info('Waiting for emulator (%r) to start... (%rms)',
|
375
|
+
self.adb_device_name(), emu_status.uptime)
|
216
376
|
if emu_status.booted:
|
217
377
|
success = True
|
218
378
|
break
|
@@ -224,18 +384,18 @@ class EmulatorSimulator(base_simulator.BaseSimulator):
|
|
224
384
|
f'The emulator failed to boot after {startup_wait_time_sec} seconds')
|
225
385
|
|
226
386
|
logging.info('Done booting the emulator (in %f seconds).', elapsed_time)
|
227
|
-
logging.info('********** Emulator logs
|
228
|
-
for line in self.get_logs().splitlines()
|
387
|
+
logging.info('********** Emulator logs **********')
|
388
|
+
for line in self.get_logs().splitlines():
|
229
389
|
logging.info(line)
|
230
390
|
logging.info('******* End of emulator logs *******')
|
231
391
|
logging.info('See the full emulator logs at %r', self._logfile_path)
|
232
392
|
|
233
393
|
@_reconnect_on_grpc_error
|
234
|
-
def send_touch(self, touches:
|
394
|
+
def send_touch(self, touches: list[tuple[int, int, bool, int]]) -> None:
|
235
395
|
"""Sends a touch event to be executed on the simulator.
|
236
396
|
|
237
397
|
Args:
|
238
|
-
touches: A list of touch events. Each
|
398
|
+
touches: A list of touch events. Each element in the list corresponds to a
|
239
399
|
single touch event. Each touch event tuple should have:
|
240
400
|
0 x: The horizontal coordinate of this event.
|
241
401
|
1 y: The vertical coordinate of this event.
|
@@ -243,7 +403,9 @@ class EmulatorSimulator(base_simulator.BaseSimulator):
|
|
243
403
|
3 identifier: Identifies a particular finger in a multitouch event.
|
244
404
|
"""
|
245
405
|
|
246
|
-
assert
|
406
|
+
assert (
|
407
|
+
self._emulator_stub is not None
|
408
|
+
), 'Emulator stub has not been initialized yet.'
|
247
409
|
touch_events = [
|
248
410
|
emulator_controller_pb2.Touch(
|
249
411
|
x=t[0], y=t[1], pressure=int(t[2]), identifier=t[3])
|
@@ -261,23 +423,32 @@ class EmulatorSimulator(base_simulator.BaseSimulator):
|
|
261
423
|
See the emulator_controller_pb2 for details.
|
262
424
|
event_type: Type of key event to be sent.
|
263
425
|
"""
|
426
|
+
|
264
427
|
event_types = emulator_controller_pb2.KeyboardEvent.KeyEventType.keys()
|
265
428
|
if event_type not in event_types:
|
266
429
|
raise ValueError(
|
267
430
|
f'Event type must be one of {event_types} but is {event_type}.')
|
268
431
|
|
432
|
+
assert (
|
433
|
+
self._emulator_stub is not None
|
434
|
+
), 'Emulator stub has not been initialized yet.'
|
269
435
|
self._emulator_stub.sendKey(
|
270
436
|
emulator_controller_pb2.KeyboardEvent(
|
271
437
|
codeType=emulator_controller_pb2.KeyboardEvent.KeyCodeType.XKB,
|
272
|
-
eventType=emulator_controller_pb2.KeyboardEvent
|
273
|
-
|
274
|
-
|
275
|
-
|
438
|
+
eventType=emulator_controller_pb2.KeyboardEvent.KeyEventType.Value(
|
439
|
+
event_type
|
440
|
+
),
|
441
|
+
keyCode=int(keycode),
|
442
|
+
)
|
443
|
+
)
|
276
444
|
|
277
445
|
@_reconnect_on_grpc_error
|
278
|
-
def
|
446
|
+
def _get_screenshot_impl(self) -> np.ndarray:
|
279
447
|
"""Fetches the latest screenshot from the emulator."""
|
280
|
-
|
448
|
+
|
449
|
+
assert (
|
450
|
+
self._emulator_stub is not None
|
451
|
+
), 'Emulator stub has not been initialized yet.'
|
281
452
|
assert self._image_format, 'ImageFormat has not been initialized yet.'
|
282
453
|
image_proto = self._emulator_stub.getScreenshot(self._image_format)
|
283
454
|
h, w = image_proto.format.height, image_proto.format.width
|
@@ -288,18 +459,28 @@ class EmulatorSimulator(base_simulator.BaseSimulator):
|
|
288
459
|
@_reconnect_on_grpc_error
|
289
460
|
def _shutdown_emulator(self):
|
290
461
|
"""Sends a signal to trigger emulator shutdown."""
|
291
|
-
|
462
|
+
|
463
|
+
if self._emulator_stub is None:
|
464
|
+
logging.info('Emulator (%r) is not up.', self.adb_device_name())
|
465
|
+
return
|
466
|
+
|
467
|
+
assert self._launcher is not None, 'Launcher is already down.'
|
468
|
+
|
469
|
+
logging.info('Shutting down the emulator (%r)...', self.adb_device_name())
|
292
470
|
self._emulator_stub.setVmState(
|
293
471
|
emulator_controller_pb2.VmRunState(
|
294
472
|
state=emulator_controller_pb2.VmRunState.RunState.SHUTDOWN))
|
295
473
|
self._launcher.confirm_shutdown()
|
296
474
|
|
297
475
|
def close(self):
|
476
|
+
super().close()
|
477
|
+
|
298
478
|
if self._launcher is not None:
|
299
479
|
self._shutdown_emulator()
|
480
|
+
logging.info('Closing emulator (%r)', self.adb_device_name())
|
300
481
|
self._launcher.close()
|
301
|
-
|
302
|
-
|
482
|
+
self._emulator_stub = None
|
483
|
+
self._snapshot_stub = None
|
303
484
|
if self._channel is not None:
|
304
485
|
self._channel.close()
|
305
486
|
super().close()
|