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
@@ -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.
@@ -20,7 +20,6 @@ import re
20
20
  import subprocess
21
21
  import sys
22
22
  import tempfile
23
- from typing import List, Optional, Tuple
24
23
 
25
24
  from absl import logging
26
25
  from android_env.components import adb_controller as adb_control
@@ -41,9 +40,8 @@ _BUTTON_TO_KEYCODE = {
41
40
  class AdbCallParser:
42
41
  """Parses AdbRequest messages and executes corresponding adb commands."""
43
42
 
44
- def __init__(self, adb_controller: adb_control.AdbController, tmp_dir: str):
43
+ def __init__(self, adb_controller: adb_control.AdbController):
45
44
  self._adb_controller = adb_controller
46
- self._tmp_dir = tmp_dir
47
45
  self._handlers = {
48
46
  'install_apk': self._install_apk,
49
47
  'start_activity': self._start_activity,
@@ -51,7 +49,7 @@ class AdbCallParser:
51
49
  'tap': self._tap,
52
50
  'press_button': self._press_button,
53
51
  'start_screen_pinning': self._start_screen_pinning,
54
- 'start_intent': self._start_intent,
52
+ 'send_broadcast': self._send_broadcast,
55
53
  'uninstall_package': self._handle_uninstall_package,
56
54
  'get_current_activity': self._get_current_activity,
57
55
  'get_orientation': self._get_orientation,
@@ -65,8 +63,8 @@ class AdbCallParser:
65
63
  }
66
64
 
67
65
  def _execute_command(
68
- self, command_args: List[str],
69
- timeout: Optional[float]) -> Tuple[adb_pb2.AdbResponse, bytes]:
66
+ self, command_args: list[str], timeout: float | None
67
+ ) -> tuple[adb_pb2.AdbResponse, bytes]:
70
68
  """Executes the command, catches errors and populates the response status.
71
69
 
72
70
  Args:
@@ -74,7 +72,7 @@ class AdbCallParser:
74
72
  timeout: Timeout in seconds.
75
73
 
76
74
  Returns:
77
- A Tuple of the AdbResponse with the status populated, and the output
75
+ A tuple of the AdbResponse with the status populated, and the output
78
76
  bytes from the command.
79
77
  """
80
78
  response = adb_pb2.AdbResponse(status=adb_pb2.AdbResponse.Status.OK)
@@ -109,12 +107,12 @@ class AdbCallParser:
109
107
  f'Got: {request.timeout_sec}')
110
108
  return response
111
109
 
112
- timeout: Optional[float] = request.timeout_sec or None
110
+ timeout: float | None = request.timeout_sec or None
113
111
  return self._handlers[command_type](request, timeout)
114
112
 
115
- def _force_stop(self,
116
- request: adb_pb2.AdbRequest,
117
- timeout: Optional[float] = None) -> adb_pb2.AdbResponse:
113
+ def _force_stop(
114
+ self, request: adb_pb2.AdbRequest, timeout: float | None = None
115
+ ) -> adb_pb2.AdbResponse:
118
116
  """Stops an application.
119
117
 
120
118
  Args:
@@ -137,9 +135,9 @@ class AdbCallParser:
137
135
 
138
136
  return response
139
137
 
140
- def _fetch_current_task_id(self,
141
- full_activity_name: str,
142
- timeout: Optional[float] = None) -> int:
138
+ def _fetch_current_task_id(
139
+ self, full_activity_name: str, timeout: float | None = None
140
+ ) -> int:
143
141
  """Returns the task ID of the given `full_activity_name`.
144
142
 
145
143
  Args:
@@ -185,9 +183,8 @@ class AdbCallParser:
185
183
  return -1
186
184
 
187
185
  def _start_screen_pinning(
188
- self,
189
- request: adb_pb2.AdbRequest,
190
- timeout: Optional[float] = None) -> adb_pb2.AdbResponse:
186
+ self, request: adb_pb2.AdbRequest, timeout: float | None = None
187
+ ) -> adb_pb2.AdbResponse:
191
188
  """Pins an application.
192
189
 
193
190
  Args:
@@ -219,39 +216,42 @@ class AdbCallParser:
219
216
 
220
217
  return response
221
218
 
222
- def _start_intent(self,
223
- request: adb_pb2.AdbRequest,
224
- timeout: Optional[float] = None) -> adb_pb2.AdbResponse:
225
- """Starts an intent.
219
+ def _send_broadcast(
220
+ self, request: adb_pb2.AdbRequest, timeout: float | None = None
221
+ ) -> adb_pb2.AdbResponse:
222
+ """Sends a broadcast.
226
223
 
227
224
  Args:
228
- request: The request with information on what intent to issue.
225
+ request: The request with the information for the broadcast event.
229
226
  timeout: Optional time limit in seconds.
230
227
 
231
228
  Returns:
232
229
  An AdbResponse.
233
230
  """
234
231
 
235
- start_intent = request.start_intent
232
+ send_broadcast = request.send_broadcast
236
233
  response = adb_pb2.AdbResponse(status=adb_pb2.AdbResponse.Status.OK)
237
- if (not start_intent.data_uri or not start_intent.action or
238
- not start_intent.package_name):
234
+ if not send_broadcast.action:
239
235
  response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
240
- response.error_message = (
241
- '`start_intent.{data_uri, action, package_name}` cannot be empty.')
236
+ response.error_message = ('`send_broadcast.{action}` cannot be empty.')
242
237
  return response
243
238
 
244
- response, _ = self._execute_command([
245
- 'shell', 'am', 'start', '-a', start_intent.action, '-d',
246
- start_intent.data_uri, start_intent.package_name
247
- ],
248
- timeout=timeout)
239
+ if send_broadcast.component:
240
+ component_args = ['-n', send_broadcast.component]
241
+ else:
242
+ component_args = []
243
+
244
+ response, _ = self._execute_command(
245
+ ['shell', 'am', 'broadcast', '-a', send_broadcast.action]
246
+ + component_args,
247
+ timeout=timeout,
248
+ )
249
249
 
250
250
  return response
251
251
 
252
- def _install_apk(self,
253
- request: adb_pb2.AdbRequest,
254
- timeout: Optional[float] = None) -> adb_pb2.AdbResponse:
252
+ def _install_apk(
253
+ self, request: adb_pb2.AdbRequest, timeout: float | None = None
254
+ ) -> adb_pb2.AdbResponse:
255
255
  """Installs an app given its local path in the filesystem.
256
256
 
257
257
  Args:
@@ -267,25 +267,46 @@ class AdbCallParser:
267
267
  response = adb_pb2.AdbResponse()
268
268
  location_type = install_apk.WhichOneof('location')
269
269
  logging.info('location_type: %s', location_type)
270
- if location_type != 'filesystem':
271
- response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
272
- response.error_message = (
273
- f'Unsupported `install_apk.location` type: {location_type}')
274
- return response
275
270
 
276
- fpath = install_apk.filesystem.path
277
- if not os.path.exists(fpath):
278
- response.status = adb_pb2.AdbResponse.Status.INTERNAL_ERROR
279
- response.error_message = f'Could not find local_apk_path: {fpath}'
280
- return response
271
+ match location_type:
272
+ case 'filesystem':
273
+ fpath = install_apk.filesystem.path
274
+ if not os.path.exists(fpath):
275
+ response.status = adb_pb2.AdbResponse.Status.INTERNAL_ERROR
276
+ response.error_message = f'Could not find local_apk_path: {fpath}'
277
+ return response
278
+
279
+ response, _ = self._execute_command(
280
+ ['install', '-r', '-t', '-g', fpath], timeout=timeout
281
+ )
282
+ case 'blob':
283
+
284
+ # `delete_on_close` was only added in Python 3.12 so we add a switch
285
+ # here to still support previous Python versions.
286
+ if sys.version_info >= (3, 12):
287
+ kwargs = {'suffix': '.apk', 'delete_on_close': False}
288
+ else:
289
+ kwargs = {'suffix': '.apk'}
290
+
291
+ with tempfile.NamedTemporaryFile(**kwargs) as f:
292
+ fpath = f.name
293
+ f.write(install_apk.blob.contents)
294
+
295
+ response, _ = self._execute_command(
296
+ ['install', '-r', '-t', '-g', fpath], timeout=timeout
297
+ )
298
+ case _:
299
+ response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
300
+ response.error_message = (
301
+ f'Unsupported `install_apk.location` type: {location_type}'
302
+ )
303
+ return response
281
304
 
282
- response, _ = self._execute_command(['install', '-r', '-t', '-g', fpath],
283
- timeout=timeout)
284
305
  return response
285
306
 
286
- def _start_activity(self,
287
- request: adb_pb2.AdbRequest,
288
- timeout: Optional[float] = None) -> adb_pb2.AdbResponse:
307
+ def _start_activity(
308
+ self, request: adb_pb2.AdbRequest, timeout: float | None = None
309
+ ) -> adb_pb2.AdbResponse:
289
310
  """Starts a given activity.
290
311
 
291
312
  Options for `start_activity`:
@@ -331,9 +352,9 @@ class AdbCallParser:
331
352
  response.start_activity.output = command_output
332
353
  return response
333
354
 
334
- def _press_button(self,
335
- request: adb_pb2.AdbRequest,
336
- timeout: Optional[float] = None) -> adb_pb2.AdbResponse:
355
+ def _press_button(
356
+ self, request: adb_pb2.AdbRequest, timeout: float | None = None
357
+ ) -> adb_pb2.AdbResponse:
337
358
  """Presses a keyboard key.
338
359
 
339
360
  Args:
@@ -359,9 +380,8 @@ class AdbCallParser:
359
380
  return response
360
381
 
361
382
  def _handle_uninstall_package(
362
- self,
363
- request: adb_pb2.AdbRequest,
364
- timeout: Optional[float] = None) -> adb_pb2.AdbResponse:
383
+ self, request: adb_pb2.AdbRequest, timeout: float | None = None
384
+ ) -> adb_pb2.AdbResponse:
365
385
  """Handles UninstallPackage messages.
366
386
 
367
387
  Args:
@@ -399,9 +419,8 @@ class AdbCallParser:
399
419
  return response
400
420
 
401
421
  def _get_current_activity(
402
- self,
403
- request: adb_pb2.AdbRequest,
404
- timeout: Optional[float] = None) -> adb_pb2.AdbResponse:
422
+ self, request: adb_pb2.AdbRequest, timeout: float | None = None
423
+ ) -> adb_pb2.AdbResponse:
405
424
  """Fetches current activity.
406
425
 
407
426
  Args:
@@ -453,9 +472,9 @@ class AdbCallParser:
453
472
  response.get_current_activity.full_activity = matches.group(1)
454
473
  return response
455
474
 
456
- def _get_orientation(self,
457
- request: adb_pb2.AdbRequest,
458
- timeout: Optional[float] = None) -> adb_pb2.AdbResponse:
475
+ def _get_orientation(
476
+ self, request: adb_pb2.AdbRequest, timeout: float | None = None
477
+ ) -> adb_pb2.AdbResponse:
459
478
  """Fetches current device orientation.
460
479
 
461
480
  Args:
@@ -491,23 +510,30 @@ class AdbCallParser:
491
510
  if physical_width:
492
511
  skip_next = int(physical_width.group(1)) < 0
493
512
 
494
- surface_orientation = re.match(r'\s+SurfaceOrientation:\s+(\d)', line)
513
+ surface_orientation = re.match(
514
+ r'\s+(SurfaceOrientation|InputDeviceOrientation):\s+(\d)', line
515
+ )
516
+
495
517
  if surface_orientation is not None:
496
518
  if skip_next:
497
519
  continue
498
- orientation = surface_orientation.group(1)
520
+ if surface_orientation.re.groups < 2:
521
+ continue
522
+ orientation = surface_orientation.group(2)
499
523
  logging.info('Done getting orientation: %r', orientation)
500
524
  response.get_orientation.orientation = int(orientation)
501
525
  return response
502
526
 
503
527
  response.status = adb_pb2.AdbResponse.Status.INTERNAL_ERROR
504
- response.error_message = ('Could not find SurfaceOrientation in dumpsys '
505
- 'output')
528
+ response.error_message = (
529
+ 'Could not find SurfaceOrientation/InputDeviceOrientation in dumpsys '
530
+ 'output'
531
+ )
506
532
  return response
507
533
 
508
- def _push(self,
509
- request: adb_pb2.AdbRequest,
510
- timeout: Optional[float] = None) -> adb_pb2.AdbResponse:
534
+ def _push(
535
+ self, request: adb_pb2.AdbRequest, timeout: float | None = None
536
+ ) -> adb_pb2.AdbResponse:
511
537
  """Uploads contents to the device.
512
538
 
513
539
  Args:
@@ -525,7 +551,7 @@ class AdbCallParser:
525
551
  error_message='Push.path is empty.')
526
552
 
527
553
  # Create temporary file with `push` contents.
528
- with tempfile.NamedTemporaryFile(dir=self._tmp_dir, delete=False) as f:
554
+ with tempfile.NamedTemporaryFile(delete=False) as f:
529
555
  fname = f.name
530
556
  f.write(request.push.content)
531
557
  # Issue `adb push` command to upload file.
@@ -536,9 +562,9 @@ class AdbCallParser:
536
562
 
537
563
  return response
538
564
 
539
- def _pull(self,
540
- request: adb_pb2.AdbRequest,
541
- timeout: Optional[float] = None) -> adb_pb2.AdbResponse:
565
+ def _pull(
566
+ self, request: adb_pb2.AdbRequest, timeout: float | None = None
567
+ ) -> adb_pb2.AdbResponse:
542
568
  """Downloads file content from the device.
543
569
 
544
570
  Args:
@@ -556,7 +582,7 @@ class AdbCallParser:
556
582
  error_message='Pull.path is empty.')
557
583
 
558
584
  # Issue `adb pull` command to copy it to a temporary file.
559
- with tempfile.NamedTemporaryFile(dir=self._tmp_dir, delete=False) as f:
585
+ with tempfile.NamedTemporaryFile(delete=False) as f:
560
586
  fname = f.name
561
587
  logging.info('Downloading %r to %r.', path, fname)
562
588
  response, _ = self._execute_command(['pull', path, fname],
@@ -569,9 +595,9 @@ class AdbCallParser:
569
595
 
570
596
  return response
571
597
 
572
- def _input_text(self,
573
- request: adb_pb2.AdbRequest,
574
- timeout: Optional[float] = None) -> adb_pb2.AdbResponse:
598
+ def _input_text(
599
+ self, request: adb_pb2.AdbRequest, timeout: float | None = None
600
+ ) -> adb_pb2.AdbResponse:
575
601
  """Inserts text as keyboard events.
576
602
 
577
603
  Args:
@@ -592,9 +618,9 @@ class AdbCallParser:
592
618
  timeout=timeout)
593
619
  return response
594
620
 
595
- def _tap(self,
596
- request: adb_pb2.AdbRequest,
597
- timeout: Optional[float] = None) -> adb_pb2.AdbResponse:
621
+ def _tap(
622
+ self, request: adb_pb2.AdbRequest, timeout: float | None = None
623
+ ) -> adb_pb2.AdbResponse:
598
624
  """Taps the device screen.
599
625
 
600
626
  Args:
@@ -622,9 +648,9 @@ class AdbCallParser:
622
648
 
623
649
  return response
624
650
 
625
- def _handle_settings(self,
626
- request: adb_pb2.AdbRequest,
627
- timeout: Optional[float] = None) -> adb_pb2.AdbResponse:
651
+ def _handle_settings(
652
+ self, request: adb_pb2.AdbRequest, timeout: float | None = None
653
+ ) -> adb_pb2.AdbResponse:
628
654
  """Handles SettingsRequest messages.
629
655
 
630
656
  Args:
@@ -647,70 +673,84 @@ class AdbCallParser:
647
673
  namespace = adb_pb2.AdbRequest.SettingsRequest.Namespace.Name(
648
674
  request.name_space).lower()
649
675
 
650
- verb = request.WhichOneof('verb')
651
- if verb == 'get':
652
- get = request.get
653
- if not get.key:
654
- response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
655
- response.error_message = (
656
- f'Empty SettingsRequest.get.key. Got: {request}.')
657
- return response
658
- response, command_output = self._execute_command(
659
- ['shell', 'settings', 'get', namespace, get.key], timeout=timeout)
660
- response.settings.output = command_output
661
- elif verb == 'put':
662
- put = request.put
663
- if not put.key or not put.value:
664
- response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
665
- response.error_message = (
666
- f'Empty SettingsRequest.put key or value. Got: {request}.')
667
- return response
668
- response, command_output = self._execute_command(
669
- ['shell', 'settings', 'put', namespace, put.key, put.value],
670
- timeout=timeout)
671
- response.settings.output = command_output
672
- elif verb == 'delete_key':
673
- delete = request.delete_key
674
- if not delete.key:
675
- response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
676
- response.error_message = (
677
- f'Empty SettingsRequest.delete_key.key. Got: {request}.')
678
- return response
679
- response, command_output = self._execute_command(
680
- ['shell', 'settings', 'delete', namespace, delete.key],
681
- timeout=timeout)
682
- response.settings.output = command_output
683
- elif verb == 'reset':
684
- reset = request.reset
685
- # At least one of `package_name` or `mode` should be given.
686
- if (not reset.package_name and
687
- reset.mode == adb_pb2.AdbRequest.SettingsRequest.Reset.Mode.UNKNOWN):
676
+ match request.WhichOneof('verb'):
677
+ case 'get':
678
+ get = request.get
679
+ if not get.key:
680
+ response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
681
+ response.error_message = (
682
+ f'Empty SettingsRequest.get.key. Got: {request}.'
683
+ )
684
+ return response
685
+ response, command_output = self._execute_command(
686
+ ['shell', 'settings', 'get', namespace, get.key], timeout=timeout
687
+ )
688
+ response.settings.output = command_output
689
+ case 'put':
690
+ put = request.put
691
+ if not put.key or not put.value:
692
+ response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
693
+ response.error_message = (
694
+ f'Empty SettingsRequest.put key or value. Got: {request}.'
695
+ )
696
+ return response
697
+ response, command_output = self._execute_command(
698
+ ['shell', 'settings', 'put', namespace, put.key, put.value],
699
+ timeout=timeout,
700
+ )
701
+ response.settings.output = command_output
702
+ case 'delete_key':
703
+ delete = request.delete_key
704
+ if not delete.key:
705
+ response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
706
+ response.error_message = (
707
+ f'Empty SettingsRequest.delete_key.key. Got: {request}.'
708
+ )
709
+ return response
710
+ response, command_output = self._execute_command(
711
+ ['shell', 'settings', 'delete', namespace, delete.key],
712
+ timeout=timeout,
713
+ )
714
+ response.settings.output = command_output
715
+ case 'reset':
716
+ reset = request.reset
717
+ # At least one of `package_name` or `mode` should be given.
718
+ if (
719
+ not reset.package_name
720
+ and reset.mode
721
+ == adb_pb2.AdbRequest.SettingsRequest.Reset.Mode.UNKNOWN
722
+ ):
723
+ response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
724
+ response.error_message = (
725
+ 'At least one of SettingsRequest.reset package_name or mode'
726
+ f' should be given. Got: {request}.'
727
+ )
728
+ return response
729
+
730
+ mode = adb_pb2.AdbRequest.SettingsRequest.Reset.Mode.Name(
731
+ reset.mode
732
+ ).lower()
733
+ arg = reset.package_name or mode
734
+ response, command_output = self._execute_command(
735
+ ['shell', 'settings', 'reset', namespace, arg], timeout=timeout
736
+ )
737
+ response.settings.output = command_output
738
+ case 'list':
739
+ response, command_output = self._execute_command(
740
+ ['shell', 'settings', 'list', namespace], timeout=timeout
741
+ )
742
+ response.settings.output = command_output
743
+ case _:
688
744
  response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
689
745
  response.error_message = (
690
- 'At least one of SettingsRequest.reset package_name or mode should '
691
- f'be given. Got: {request}.')
692
- return response
693
-
694
- mode = adb_pb2.AdbRequest.SettingsRequest.Reset.Mode.Name(
695
- reset.mode).lower()
696
- arg = reset.package_name or mode
697
- response, command_output = self._execute_command(
698
- ['shell', 'settings', 'reset', namespace, arg], timeout=timeout)
699
- response.settings.output = command_output
700
- elif verb == 'list':
701
- response, command_output = self._execute_command(
702
- ['shell', 'settings', 'list', namespace], timeout=timeout)
703
- response.settings.output = command_output
704
- else:
705
- response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
706
- response.error_message = (
707
- f'Unknown SettingsRequest.verb. Got: {request}.')
746
+ f'Unknown SettingsRequest.verb. Got: {request}.'
747
+ )
708
748
 
709
749
  return response
710
750
 
711
- def _handle_generic(self,
712
- request: adb_pb2.AdbRequest,
713
- timeout: Optional[float] = None) -> adb_pb2.AdbResponse:
751
+ def _handle_generic(
752
+ self, request: adb_pb2.AdbRequest, timeout: float | None = None
753
+ ) -> adb_pb2.AdbResponse:
714
754
  """Handles GenericRequest messages.
715
755
 
716
756
  Args:
@@ -728,9 +768,8 @@ class AdbCallParser:
728
768
  return response
729
769
 
730
770
  def _handle_package_manager(
731
- self,
732
- request: adb_pb2.AdbRequest,
733
- timeout: Optional[float] = None) -> adb_pb2.AdbResponse:
771
+ self, request: adb_pb2.AdbRequest, timeout: float | None = None
772
+ ) -> adb_pb2.AdbResponse:
734
773
  """Handles PackageManagerRequest messages.
735
774
 
736
775
  Args:
@@ -745,60 +784,64 @@ class AdbCallParser:
745
784
  request = request.package_manager
746
785
  response = adb_pb2.AdbResponse()
747
786
 
748
- verb = request.WhichOneof('verb')
749
- if verb == 'list':
750
- what = request.list.WhichOneof('what')
751
- response, output = self._execute_command(['shell', 'pm', 'list', what],
752
- timeout=timeout)
753
-
754
- if output:
755
- items = output.decode('utf-8').split()
756
- # Remove prefix for each item.
757
- prefix = {
758
- 'features': 'feature:',
759
- 'libraries': 'library:',
760
- 'packages': 'package:',
761
- }[what]
762
- items = [x[len(prefix):] for x in items if x.startswith(prefix)]
763
- response.package_manager.list.items.extend(items)
764
- response.package_manager.output = output
765
- elif verb == 'clear':
766
- package_name = request.clear.package_name
767
- if not package_name:
768
- response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
769
- response.error_message = (
770
- f'Empty PackageManagerRequest.clear.package_name. Got: {request}.')
771
- return response
772
-
773
- args = ['shell', 'pm', 'clear', package_name]
774
- if request.clear.user_id:
775
- args.insert(3, '-f')
776
- args.insert(4, request.clear.user_id)
777
- response, response.package_manager.output = self._execute_command(
778
- args, timeout=timeout)
779
- elif verb == 'grant':
780
- grant = request.grant
781
- if not grant.package_name:
782
- response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
783
- response.error_message = ('`grant.package_name` cannot be empty.')
784
- return response
785
-
786
- if not grant.permissions:
787
- response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
788
- response.error_message = ('`grant.permissions` cannot be empty.')
789
- return response
790
-
791
- for permission in grant.permissions:
792
- logging.info('Granting permission: %r', permission)
787
+ match request.WhichOneof('verb'):
788
+ case 'list':
789
+ what = request.list.WhichOneof('what')
790
+ response, output = self._execute_command(
791
+ ['shell', 'pm', 'list', what], timeout=timeout
792
+ )
793
+
794
+ if output:
795
+ items = output.decode('utf-8').split()
796
+ # Remove prefix for each item.
797
+ prefix = {
798
+ 'features': 'feature:',
799
+ 'libraries': 'library:',
800
+ 'packages': 'package:',
801
+ }[what]
802
+ items = [x[len(prefix) :] for x in items if x.startswith(prefix)]
803
+ response.package_manager.list.items.extend(items)
804
+ response.package_manager.output = output
805
+ case 'clear':
806
+ package_name = request.clear.package_name
807
+ if not package_name:
808
+ response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
809
+ response.error_message = (
810
+ f'Empty PackageManagerRequest.clear.package_name. Got: {request}.'
811
+ )
812
+ return response
813
+
814
+ args = ['shell', 'pm', 'clear', package_name]
815
+ if request.clear.user_id:
816
+ args.insert(3, '-f')
817
+ args.insert(4, request.clear.user_id)
793
818
  response, response.package_manager.output = self._execute_command(
794
- ['shell', 'pm', 'grant', grant.package_name, permission],
795
- timeout=timeout)
819
+ args, timeout=timeout
820
+ )
821
+ case 'grant':
822
+ grant = request.grant
823
+ if not grant.package_name:
824
+ response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
825
+ response.error_message = '`grant.package_name` cannot be empty.'
826
+ return response
827
+
828
+ if not grant.permissions:
829
+ response.status = adb_pb2.AdbResponse.Status.FAILED_PRECONDITION
830
+ response.error_message = '`grant.permissions` cannot be empty.'
831
+ return response
832
+
833
+ for permission in grant.permissions:
834
+ logging.info('Granting permission: %r', permission)
835
+ response, response.package_manager.output = self._execute_command(
836
+ ['shell', 'pm', 'grant', grant.package_name, permission],
837
+ timeout=timeout,
838
+ )
796
839
 
797
840
  return response
798
841
 
799
- def _handle_dumpsys(self,
800
- request: adb_pb2.AdbRequest,
801
- timeout: Optional[float] = None) -> adb_pb2.AdbResponse:
842
+ def _handle_dumpsys(
843
+ self, request: adb_pb2.AdbRequest, timeout: float | None = None
844
+ ) -> adb_pb2.AdbResponse:
802
845
  """Handles DumpsysRequest messages.
803
846
 
804
847
  Args: