Skip to content

Commit f7f03d4

Browse files
authored
chore: major clean up in code base related to adb command execution (#307)
Also, fix screen recording which was broken
1 parent a1b02ff commit f7f03d4

File tree

3 files changed

+92
-85
lines changed

3 files changed

+92
-85
lines changed

Makefile

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
lint: lint_python lint_markdown
22

3-
test: test_python3
3+
test: test_python
44

55
documentation:
66
pandoc --from=markdown --to=rst --output=docs/README.rst README.md && cd docs && make html
@@ -41,12 +41,9 @@ lint_python:
4141
uv run ruff check --config pyproject.toml adbe
4242
# E0602 is due to undefined variable unicode which is defined only for Python 2
4343
# W0511 is fixme due to TODOs in the code.
44-
# adbe/adbe.py:756:8: W0601: Global variable 'screen_record_file_path_on_device' undefined at the module level (global-variable-undefined)
45-
# adbe/adbe.py:764:8: W0601: Global variable 'screen_record_file_path_on_device' undefined at the module level (global-variable-undefined)
46-
# adbe/adbe.py:752:4: W0621: Redefining name 'screen_record_file_path_on_device' from outer scope (line 759) (redefined-outer-name)
4744
# C0111: Missing function docstring (missing-docstring)
4845
uv run -- pylint --disable=C0103,C0111,C0209,W1514 release.py
49-
uv run -- pylint adbe/*.py tests/*.py --disable=R0123,R0911,R0912,R0914,R0915,R1705,R1710,C0103,C0111,C0209,C0301,C0302,C1801,W0511,W0621,W0601,W0602,W0603
46+
uv run -- pylint adbe/*.py tests/*.py --disable=R0123,R0911,R0912,R0914,R0915,R1705,R1710,C0103,C0111,C0209,C0301,C0302,C1801,W0511,W0602,W0603
5047
uv run -- flake8 adbe --count --ignore=F401,E126,E501,W503 --show-source --statistics
5148
# Default complexity limit is 10
5249
# Default line length limit is 127

adbe/adb_enhanced.py

Lines changed: 45 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
execute_adb_command2,
2828
execute_adb_shell_command,
2929
execute_adb_shell_command2,
30+
execute_adb_shell_command3,
3031
execute_file_related_adb_shell_command,
3132
get_adb_shell_property,
3233
get_device_android_api_version,
@@ -49,6 +50,7 @@
4950
execute_adb_command2,
5051
execute_adb_shell_command,
5152
execute_adb_shell_command2,
53+
execute_adb_shell_command3,
5254
execute_file_related_adb_shell_command,
5355
get_adb_shell_property,
5456
get_device_android_api_version,
@@ -432,13 +434,12 @@ def handle_list_devices() -> None:
432434

433435
def _get_device_serials() -> list[str]:
434436
cmd = "devices -l"
435-
return_code, stdout, stderr = execute_adb_command2(cmd)
436-
if return_code != 0:
437-
print_error_and_exit(f"Failed to execute command {cmd}, error: {stderr} ")
437+
result = execute_adb_command2(cmd)
438+
assert result.return_code == 0, f"Failed to execute command {cmd}, error: {result.stderr}"
438439

439440
device_serials = []
440441
# Skip the first line, it says "List of devices attached"
441-
device_infos = stdout.split("\n")[1:]
442+
device_infos = result.stdout.split("\n")[1:]
442443

443444
if len(device_infos) == 0 or (
444445
len(device_infos) == 1 and len(device_infos[0]) == 0):
@@ -522,18 +523,16 @@ def dump_ui(xml_file: str) -> None:
522523
cmd3 = f"rm {tmp_file}"
523524

524525
print_verbose(f"Writing UI to {tmp_file}")
525-
return_code, _, stderr = execute_adb_shell_command2(cmd1)
526-
if return_code != 0:
527-
print_error_and_exit(f'Failed to execute "{cmd1}", stderr: "{stderr}"')
526+
result = execute_adb_shell_command3(cmd1)
527+
assert result.return_code == 0, f"Failed to execute command {cmd1}, error: {result.stderr}"
528528

529529
print_verbose(f"Pulling file {xml_file}")
530-
return_code, _, stderr = execute_adb_command2(cmd2)
530+
result = execute_adb_command2(cmd2)
531+
assert result.return_code == 0, f"Failed to execute command {cmd2}, error: {result.stderr}"
531532
print_verbose(f"Deleting file {tmp_file}")
532-
execute_adb_shell_command2(cmd3)
533-
if return_code != 0:
534-
print_error_and_exit(f"Failed to fetch file {tmp_file}")
535-
else:
536-
print_message(f'XML UI dumped to {xml_file}, you might want to format it using "xmllint --format {xml_file}"')
533+
result = execute_adb_shell_command3(cmd3)
534+
assert result.return_code == 0, f"Failed to execute command {cmd3}, error: {result.stderr}"
535+
print_message(f'XML UI dumped to {xml_file}, you might want to format it using "xmllint --format {xml_file}"')
537536

538537

539538
@ensure_package_exists
@@ -611,14 +610,14 @@ def force_rtl(*, turn_on: bool) -> None:
611610
def dump_screenshot(filepath: str) -> None:
612611
screenshot_file_path_on_device = _create_tmp_file("screenshot", "png")
613612
dump_cmd = f"screencap -p {screenshot_file_path_on_device} "
614-
return_code, stdout, stderr = execute_adb_shell_command2(dump_cmd)
615-
if return_code != 0:
616-
print_error_and_exit(
617-
f"Failed to capture the screenshot: (stdout: {stdout}, stderr: {stderr})")
613+
result = execute_adb_shell_command3(dump_cmd)
614+
assert result.return_code == 0, f"Failed to capture screenshot, error: {result.stderr}"
618615
pull_cmd = f"pull {screenshot_file_path_on_device} {filepath}"
619-
execute_adb_command2(pull_cmd)
616+
result = execute_adb_command2(pull_cmd)
617+
assert result.return_code == 0, f"Failed to pull screenshot file, error: {result.stderr}"
620618
del_cmd = f"rm {screenshot_file_path_on_device}"
621-
execute_adb_shell_command2(del_cmd)
619+
result = execute_adb_shell_command3(del_cmd)
620+
assert result.return_code == 0, f"Failed to delete screenshot file from device, error: {result.stderr}"
622621

623622

624623
def dump_screenrecord(filepath: str) -> None:
@@ -632,12 +631,10 @@ def dump_screenrecord(filepath: str) -> None:
632631

633632
original_sigint_handler = None
634633

635-
def _start_recording() -> str:
634+
def _start_recording(screen_record_file_path: str) -> None:
636635
print_message("Recording video, press Ctrl+C to end...")
637-
tmp_file_path = _create_tmp_file("screenrecord", "mp4")
638-
dump_cmd = f"screenrecord --verbose {tmp_file_path} "
636+
dump_cmd = f"screenrecord --verbose {screen_record_file_path} "
639637
execute_adb_shell_command2(dump_cmd)
640-
return tmp_file_path
641638

642639
def _pull_and_delete_file_from_device(screen_record_file_path: str) -> None:
643640
print_message(f"Saving recording to {filepath}")
@@ -674,7 +671,8 @@ def signal_handler(_sig: int, _frame: Any) -> None:
674671
original_sigint_handler = signal.getsignal(signal.SIGINT)
675672
signal.signal(signal.SIGINT, signal_handler)
676673

677-
screen_record_file_path_on_device = _start_recording()
674+
screen_record_file_path_on_device = _create_tmp_file("screenrecord", "mp4")
675+
_start_recording(screen_record_file_path_on_device)
678676

679677

680678
def get_mobile_data_saver_state() -> str:
@@ -1388,9 +1386,9 @@ def push_file(local_file_path: str, remote_file_path: str) -> None:
13881386
cp_cmd = f"cp {tmp_file} {remote_file_path}"
13891387
rm_cmd = f"rm {tmp_file}"
13901388

1391-
return_code, _, stderr = execute_adb_command2(push_cmd)
1392-
if return_code != 0:
1393-
print_error_and_exit(f"Failed to push file, error: {stderr}")
1389+
result = execute_adb_command2(push_cmd)
1390+
if result.return_code != 0:
1391+
print_error_and_exit(f"Failed to push file, error: {result.stderr}")
13941392
return
13951393

13961394
execute_file_related_adb_shell_command(cp_cmd, remote_file_path)
@@ -1590,9 +1588,9 @@ def print_app_signature(app_name: str) -> None:
15901588
with tempfile.NamedTemporaryFile(prefix=app_name, suffix=".apk") as tmp_apk_file:
15911589
tmp_apk_file_name = tmp_apk_file.name
15921590
adb_cmd = f"pull {apk_path} {tmp_apk_file_name}"
1593-
return_code, _, stderr = execute_adb_command2(adb_cmd)
1594-
if return_code != 0:
1595-
print_error_and_exit(f"Failed to pull file {apk_path}, stderr: {stderr}")
1591+
result = execute_adb_command2(adb_cmd)
1592+
if result.return_code != 0:
1593+
print_error_and_exit(f"Failed to pull file {apk_path}, stderr: {result.stderr}")
15961594
return
15971595

15981596
dir_of_this_script = os.path.split(__file__)[0]
@@ -1669,9 +1667,9 @@ def backup_func() -> None:
16691667
def perform_install(file_path: str) -> None:
16701668
print_verbose(f"Installing {file_path}")
16711669
# -r: replace existing application
1672-
return_code, _, stderr = execute_adb_command2(f"install -r {file_path}")
1673-
if return_code != 0:
1674-
print_error(f"Failed to install {file_path}, stderr: {stderr}")
1670+
result = execute_adb_command2(f"install -r {file_path}")
1671+
if result.return_code != 0:
1672+
print_error(f"Failed to install {file_path}, stderr: {result.stderr}")
16751673

16761674

16771675
@ensure_package_exists
@@ -1697,23 +1695,9 @@ def perform_uninstall(app_name: str, first_user: bool) -> None:
16971695
print_error(f"Failed to uninstall {app_name}, stderr: {stderr}")
16981696

16991697

1700-
def _get_window_size() -> tuple[int, int]:
1701-
adb_cmd = "shell wm size"
1702-
_, result, _ = execute_adb_command2(adb_cmd)
1703-
1704-
if result is None:
1705-
return -1, -1
1706-
1707-
regex_data = re.search(r"size: ([0-9]+)x([0-9]+)", result)
1708-
if regex_data is None:
1709-
return -1, -1
1710-
1711-
return int(regex_data.group(1)), int(regex_data.group(2))
1712-
1713-
17141698
def _perform_tap(x: int, y: int) -> None:
17151699
adb_shell_cmd = f"input tap {x:d} {y:d}"
1716-
execute_adb_shell_command2(adb_shell_cmd)
1700+
execute_adb_shell_command3(adb_shell_cmd)
17171701

17181702

17191703
# Deprecated
@@ -1771,14 +1755,14 @@ def enable_wireless_debug() -> bool:
17711755

17721756
ip = matching[0]
17731757

1774-
code, _, stderr = execute_adb_command2("tcpip 5555")
1775-
if code != 0:
1758+
result = execute_adb_command2("tcpip 5555")
1759+
if result.return_code != 0:
17761760
print_error_and_exit(f"Failed to switch device {ip} to wireless debug mode, "
1777-
f"stderr: {stderr}")
1761+
f"stderr: {result.stderr}")
17781762

1779-
code, _, stderr = execute_adb_command2(f"connect {ip}")
1780-
if code != 0:
1781-
print_error_and_exit(f"Cannot enable wireless debugging. Error: {stderr}")
1763+
result = execute_adb_command2(f"connect {ip}")
1764+
if result.return_code != 0:
1765+
print_error_and_exit(f"Cannot enable wireless debugging. Error: {result.stderr}")
17821766
return False
17831767
print_message(f"Connected via IP now you can disconnect the cable\nIP: {ip}")
17841768
return True
@@ -1805,9 +1789,9 @@ def disable_wireless_debug() -> None:
18051789
result = True
18061790

18071791
for ip in ip_list:
1808-
code, _, stderr = execute_adb_command2(f"disconnect {ip}")
1809-
if code != 0:
1810-
print_error(f"Failed to disconnect {ip}: {stderr}")
1792+
result = execute_adb_command2(f"disconnect {ip}")
1793+
if result.return_code != 0:
1794+
print_error(f"Failed to disconnect {ip}: {result.stderr}")
18111795
result = False
18121796
else:
18131797
print_message(f"Disconnected {ip}")
@@ -2090,10 +2074,10 @@ def print_state_change_info(state_name: str, old_state: str | int | bool, new_st
20902074

20912075
def print_display_size() -> None:
20922076
# Ref: https://developer.android.com/training/multiscreen/screendensities
2093-
cmd = "shell wm density"
2094-
return_code, stdout, stderr = execute_adb_command2(cmd)
2095-
assert return_code == 0, f"Failed to get display size, stderr: {stderr}"
2096-
display_size = re.search(r"Physical density: (\d+)", stdout)
2077+
cmd = "wm density"
2078+
result = execute_adb_shell_command3(cmd)
2079+
assert result.return_code == 0, f"Failed to get display size, stderr: {result.stderr}"
2080+
display_size = re.search(r"Physical density: (\d+)", result.stdout)
20972081
if display_size is None:
20982082
print_error_and_exit("Failed to get display size")
20992083

adbe/adb_helper.py

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@
1111
from output_helper import print_error, print_error_and_exit, print_verbose
1212

1313

14+
@dataclasses.dataclass
15+
class CommandResult:
16+
return_code: int
17+
stdout: str | None
18+
stderr: str | None
19+
20+
1421
@dataclasses.dataclass
1522
class _Settings:
1623
adb_prefix: str = "adb"
@@ -36,20 +43,33 @@ def set_adb_prefix(adb_prefix: str) -> None:
3643

3744

3845
def get_adb_shell_property(property_name: str, device_serial: str | None = None) -> str | None:
39-
_, stdout, _ = execute_adb_shell_command2(f"getprop {property_name}", device_serial=device_serial)
40-
return stdout
46+
result = execute_adb_shell_command3(f"getprop {property_name}", device_serial=device_serial)
47+
if result.return_code != 0:
48+
print_error(f"Unable to get property {property_name} from device, return code: {result.return_code}")
49+
return None
4150

51+
return result.stdout
4252

43-
def execute_adb_shell_command2(
53+
54+
def execute_adb_shell_command3(
4455
adb_cmd: str, piped_into_cmd: bool | None = None, ignore_stderr: bool = False,
45-
device_serial: str | None = None) -> tuple[int, str | None, str]:
56+
device_serial: str | None = None) -> CommandResult:
4657
return execute_adb_command2(f"shell {adb_cmd}", piped_into_cmd=piped_into_cmd,
4758
ignore_stderr=ignore_stderr, device_serial=device_serial)
4859

4960

50-
def execute_adb_command2(
61+
# Deprecated function, use execute_adb_shell_command3 instead
62+
def execute_adb_shell_command2(
5163
adb_cmd: str, piped_into_cmd: bool | None = None, ignore_stderr: bool = False,
5264
device_serial: str | None = None) -> tuple[int, str | None, str]:
65+
result = execute_adb_shell_command3(
66+
adb_cmd, piped_into_cmd=piped_into_cmd, ignore_stderr=ignore_stderr, device_serial=device_serial)
67+
return result.return_code, result.stdout, result.stderr
68+
69+
70+
def execute_adb_command2(
71+
adb_cmd: str, piped_into_cmd: bool | None = None, ignore_stderr: bool = False,
72+
device_serial: str | None = None) -> CommandResult:
5373
"""
5474
:param adb_cmd: command to run inside the adb shell (so, don't prefix it with "adb")
5575
:param piped_into_cmd: command to pipe the output of this command into
@@ -69,10 +89,12 @@ def execute_adb_command2(
6989
with subprocess.Popen(final_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as ps1:
7090
stdout_data, stderr_data = ps1.communicate()
7191
return_code = ps1.returncode
92+
7293
try:
7394
stdout_data = stdout_data.decode("utf-8")
7495
except UnicodeDecodeError:
7596
print_error("Unable to decode data as UTF-8, defaulting to printing the binary data")
97+
7698
stderr_data = stderr_data.decode("utf-8")
7799

78100
_check_for_adb_not_found_error(stderr_data)
@@ -82,13 +104,13 @@ def execute_adb_command2(
82104
print_error(stderr_data)
83105

84106
if not stdout_data:
85-
return return_code, None, stderr_data
107+
return CommandResult(return_code=return_code, stdout=None, stderr=stderr_data)
86108

87109
# stdout_data is not None
88110
if isinstance(stdout_data, bytes):
89111
print_verbose(f'Result is "{stdout_data}"')
90-
return return_code, stdout_data, stderr_data
91-
# str for Python 3, this used to be unicode type for python 2
112+
return CommandResult(return_code=return_code, stdout=stdout_data, stderr=stderr_data)
113+
92114
if isinstance(stdout_data, str):
93115
output = ""
94116
first_line = True
@@ -104,16 +126,18 @@ def execute_adb_command2(
104126
else:
105127
output += "\n" + line
106128
print_verbose(f'Result is "{output}"')
107-
return return_code, output, stderr_data
129+
return CommandResult(return_code=return_code, stdout=output, stderr=stderr_data)
130+
108131
print_error_and_exit(f"stdout_data is weird type: {type(stdout_data)}")
109-
return None
132+
return CommandResult(return_code=return_code, stdout=None, stderr="")
110133

111134

135+
# Deprecated function, use execute_adb_shell_command3 instead
112136
def execute_adb_shell_command(adb_cmd: str, piped_into_cmd: str | None = None, ignore_stderr: bool = False,
113137
device_serial: str | None = None) -> str:
114-
_, stdout, _ = execute_adb_command2(
138+
result = execute_adb_command2(
115139
f"shell {adb_cmd}", piped_into_cmd, ignore_stderr, device_serial=device_serial)
116-
return stdout
140+
return result.stdout
117141

118142

119143
def execute_file_related_adb_shell_command(
@@ -137,19 +161,21 @@ def execute_file_related_adb_shell_command(
137161
print_verbose(f'Attempt {attempt_count}/{len(adb_cmds_prefix)}: "{adb_cmd_prefix}"')
138162
attempt_count += 1
139163
adb_cmd = f"{adb_cmd_prefix} {adb_shell_cmd}"
140-
return_code, stdout, stderr = execute_adb_command2(
164+
result = execute_adb_command2(
141165
adb_cmd, piped_into_cmd, ignore_stderr, device_serial=device_serial)
142166

143-
if stderr.find(file_not_found_message) >= 0:
167+
if result.stderr.find(file_not_found_message) >= 0:
144168
print_error(f"File not found: {file_path}")
145-
return stderr
146-
if stderr.find(is_a_directory_message) >= 0:
169+
return result.stderr
170+
if result.stderr.find(is_a_directory_message) >= 0:
147171
print_error(f"{file_path} is a directory")
148-
return stderr
172+
return result.stderr
149173

150174
api_version = get_device_android_api_version()
151-
if api_version >= _MIN_VERSION_ABOVE_WHICH_ADB_SHELL_RETURNS_CORRECT_EXIT_CODE and return_code == 0:
152-
return stdout
175+
if api_version >= _MIN_VERSION_ABOVE_WHICH_ADB_SHELL_RETURNS_CORRECT_EXIT_CODE and result.return_code == 0:
176+
return result.stdout
177+
178+
stdout = result.stdout
153179

154180
return stdout
155181

0 commit comments

Comments
 (0)