From 430d69695ccdecc7981a3d5cffcd680a7e3f588d Mon Sep 17 00:00:00 2001 From: YoursFunny Date: Mon, 6 Jul 2026 02:02:57 +0800 Subject: [PATCH] feat: add handle AdbError: unknown host service --- module/device/connection.py | 36 ++++++++++++++++++++++----- module/device/method/adb.py | 6 ++++- module/device/method/ascreencap.py | 11 +++++--- module/device/method/droidcast.py | 6 ++++- module/device/method/hermit.py | 8 ++++-- module/device/method/maatouch.py | 7 +++++- module/device/method/minitouch.py | 9 ++++++- module/device/method/scrcpy/scrcpy.py | 6 ++++- module/device/method/uiautomator_2.py | 8 ++++-- module/device/method/utils.py | 25 ++++++++++++++----- module/device/method/wsa.py | 8 ++++-- 11 files changed, 104 insertions(+), 26 deletions(-) diff --git a/module/device/connection.py b/module/device/connection.py index 6053deb..84dd75b 100644 --- a/module/device/connection.py +++ b/module/device/connection.py @@ -15,9 +15,9 @@ from module.base.decorator import Config, cached_property, del_cached_property, from module.base.utils import SelectedGrids, ensure_time from module.device.connection_attr import ConnectionAttr from module.device.env import IS_LINUX, IS_MACINTOSH, IS_WINDOWS -from module.device.method.utils import ( - PackageNotInstalled, RETRY_TRIES, get_serial_pair, handle_adb_error, - possible_reasons, random_port, recv_all, remove_shell_warning, retry_sleep) +from module.device.method.utils import (PackageNotInstalled, RETRY_TRIES, get_serial_pair, handle_adb_error, + handle_unknown_host_service, possible_reasons, random_port, recv_all, + remove_shell_warning, retry_sleep) from module.exception import EmulatorNotRunningError, RequestHumanTakeover from module.logger import logger @@ -50,6 +50,10 @@ def retry(func): if handle_adb_error(e): def init(): self.adb_reconnect() + elif handle_unknown_host_service(e): + def init(): + self.adb_start_server() + self.adb_reconnect() else: break # Package not installed @@ -142,8 +146,18 @@ class Connection(ConnectionAttr): """ cmd = list(map(str, cmd)) cmd = [self.adb_binary, '-s', self.serial] + cmd - logger.info(f'Execute: {cmd}') + return self.subprocess_run(cmd, timeout=timeout) + def subprocess_run(self, cmd, timeout=10): + """ + Args: + cmd (list): + timeout (int): + + Returns: + str: + """ + logger.info(f'Execute: {cmd}') # Use shell=True to disable console window when using GUI. # Although, there's still a window when you stop running in GUI, which cause by gooey. # To disable it, edit gooey/gui/util/taskkill.py @@ -160,11 +174,21 @@ class Connection(ConnectionAttr): @Config.when(DEVICE_OVER_HTTP=True) def adb_command(self, cmd, timeout=10): - logger.warning( - f'adb_command() is not available when connecting over http: {self.serial}, ' + logger.critical( + f'Trying to execute {cmd}, ' + f'but adb_command() is not available when connecting over http: {self.serial}, ' ) raise RequestHumanTakeover + def adb_start_server(self): + """ + Use `adb devices` as `adb start-server`, result is actually useless + Start ADB using subprocess instead of connecting via socket to kill the other ADBs + """ + stdout = self.subprocess_run([self.adb_binary, 'devices']) + logger.info(stdout) + return stdout + @Config.when(DEVICE_OVER_HTTP=False) def adb_shell(self, cmd, stream=False, recvall=True, timeout=10, rstrip=True): """ diff --git a/module/device/method/adb.py b/module/device/method/adb.py index a1d72d6..8f7a66c 100644 --- a/module/device/method/adb.py +++ b/module/device/method/adb.py @@ -10,7 +10,7 @@ from lxml import etree from module.base.decorator import Config from module.device.connection import Connection from module.device.method.utils import (ImageTruncated, PackageNotInstalled, RETRY_TRIES, handle_adb_error, - remove_prefix, retry_sleep) + handle_unknown_host_service, remove_prefix, retry_sleep) from module.exception import RequestHumanTakeover, ScriptError from module.logger import logger @@ -43,6 +43,10 @@ def retry(func): if handle_adb_error(e): def init(): self.adb_reconnect() + elif handle_unknown_host_service(e): + def init(): + self.adb_start_server() + self.adb_reconnect() else: break # Package not installed diff --git a/module/device/method/ascreencap.py b/module/device/method/ascreencap.py index 165e452..24ba990 100644 --- a/module/device/method/ascreencap.py +++ b/module/device/method/ascreencap.py @@ -6,7 +6,8 @@ from adbutils.errors import AdbError from module.base.utils import * from module.device.connection import Connection -from module.device.method.utils import (ImageTruncated, RETRY_TRIES, handle_adb_error, retry_sleep) +from module.device.method.utils import (ImageTruncated, RETRY_TRIES, handle_adb_error, handle_unknown_host_service, + retry_sleep) from module.exception import RequestHumanTakeover, ScriptError from module.logger import logger @@ -49,6 +50,10 @@ def retry(func): if handle_adb_error(e): def init(): self.adb_reconnect() + elif handle_unknown_host_service(e): + def init(): + self.adb_start_server() + self.adb_reconnect() else: break # ImageTruncated @@ -164,11 +169,11 @@ class AScreenCap(Connection): # ValueError: cannot reshape array of size 0 into shape (720,1280,4) raise ImageTruncated(str(e)) - image = cv2.flip(image, 0, dst=image) + cv2.flip(image, 0, dst=image) if image is None: raise ImageTruncated('Empty image after cv2.flip') - image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB, dst=image) + cv2.cvtColor(image, cv2.COLOR_BGR2RGB, dst=image) if image is None: raise ImageTruncated('Empty image after cv2.cvtColor') diff --git a/module/device/method/droidcast.py b/module/device/method/droidcast.py index 73c0f22..43dad33 100644 --- a/module/device/method/droidcast.py +++ b/module/device/method/droidcast.py @@ -10,7 +10,7 @@ from module.base.decorator import cached_property, del_cached_property from module.base.timer import Timer from module.device.method.uiautomator_2 import ProcessInfo, Uiautomator2 from module.device.method.utils import ( - ImageTruncated, PackageNotInstalled, RETRY_TRIES, handle_adb_error, retry_sleep) + ImageTruncated, PackageNotInstalled, RETRY_TRIES, handle_adb_error, handle_unknown_host_service, retry_sleep) from module.exception import RequestHumanTakeover from module.logger import logger @@ -47,6 +47,10 @@ def retry(func): if handle_adb_error(e): def init(): self.adb_reconnect() + elif handle_unknown_host_service(e): + def init(): + self.adb_start_server() + self.adb_reconnect() else: break # Package not installed diff --git a/module/device/method/hermit.py b/module/device/method/hermit.py index ad0c397..4519f81 100644 --- a/module/device/method/hermit.py +++ b/module/device/method/hermit.py @@ -8,8 +8,8 @@ from module.base.decorator import cached_property from module.base.timer import Timer from module.base.utils import point2str, random_rectangle_point from module.device.method.adb import Adb -from module.device.method.utils import (RETRY_TRIES, retry_sleep, - HierarchyButton, handle_adb_error) +from module.device.method.utils import (HierarchyButton, RETRY_TRIES, handle_adb_error, handle_unknown_host_service, + retry_sleep) from module.exception import RequestHumanTakeover from module.logger import logger @@ -62,6 +62,10 @@ def retry(func): if handle_adb_error(e): def init(): self.adb_reconnect() + elif handle_unknown_host_service(e): + def init(): + self.adb_start_server() + self.adb_reconnect() else: break # HermitError: {"code":-1,"msg":"error"} diff --git a/module/device/method/maatouch.py b/module/device/method/maatouch.py index 2bc81ac..8c22356 100644 --- a/module/device/method/maatouch.py +++ b/module/device/method/maatouch.py @@ -9,7 +9,7 @@ from module.base.timer import Timer from module.base.utils import * from module.device.connection import Connection from module.device.method.minitouch import CommandBuilder, insert_swipe -from module.device.method.utils import RETRY_TRIES, handle_adb_error, retry_sleep +from module.device.method.utils import RETRY_TRIES, handle_adb_error, handle_unknown_host_service, retry_sleep from module.exception import RequestHumanTakeover from module.logger import logger @@ -51,6 +51,11 @@ def retry(func): def init(): self.adb_reconnect() del_cached_property(self, '_maatouch_builder') + elif handle_unknown_host_service(e): + def init(): + self.adb_start_server() + self.adb_reconnect() + del_cached_property(self, '_maatouch_builder') else: break # MaaTouchNotInstalledError: Received "Aborted" from MaaTouch diff --git a/module/device/method/minitouch.py b/module/device/method/minitouch.py index 356976c..ec71ce0 100644 --- a/module/device/method/minitouch.py +++ b/module/device/method/minitouch.py @@ -14,7 +14,7 @@ from module.base.decorator import Config, cached_property, del_cached_property, from module.base.timer import Timer from module.base.utils import * from module.device.connection import Connection -from module.device.method.utils import RETRY_TRIES, handle_adb_error, retry_sleep +from module.device.method.utils import RETRY_TRIES, handle_adb_error, handle_unknown_host_service, retry_sleep from module.exception import RequestHumanTakeover, ScriptError from module.logger import logger @@ -354,6 +354,13 @@ def retry(func): if self._minitouch_port: self.adb_forward_remove(f'tcp:{self._minitouch_port}') del_cached_property(self, '_minitouch_builder') + elif handle_unknown_host_service(e): + def init(): + self.adb_start_server() + self.adb_reconnect() + if self._minitouch_port: + self.adb_forward_remove(f'tcp:{self._minitouch_port}') + del_cached_property(self, '_minitouch_builder') else: break except BrokenPipeError as e: diff --git a/module/device/method/scrcpy/scrcpy.py b/module/device/method/scrcpy/scrcpy.py index 0730e9b..d666a11 100644 --- a/module/device/method/scrcpy/scrcpy.py +++ b/module/device/method/scrcpy/scrcpy.py @@ -10,7 +10,7 @@ from module.base.utils import random_rectangle_point from module.device.method.minitouch import insert_swipe from module.device.method.scrcpy.core import ScrcpyCore, ScrcpyError from module.device.method.uiautomator_2 import Uiautomator2 -from module.device.method.utils import RETRY_TRIES, handle_adb_error, retry_sleep +from module.device.method.utils import RETRY_TRIES, handle_adb_error, handle_unknown_host_service, retry_sleep from module.exception import RequestHumanTakeover from module.logger import logger @@ -62,6 +62,10 @@ def retry(func): if handle_adb_error(e): def init(): self.adb_reconnect() + elif handle_unknown_host_service(e): + def init(): + self.adb_start_server() + self.adb_reconnect() else: break # Unknown, probably a trucked image diff --git a/module/device/method/uiautomator_2.py b/module/device/method/uiautomator_2.py index f4b03d2..6cb3cb8 100644 --- a/module/device/method/uiautomator_2.py +++ b/module/device/method/uiautomator_2.py @@ -10,7 +10,7 @@ from lxml import etree from module.base.utils import * from module.device.connection import Connection from module.device.method.utils import (ImageTruncated, PackageNotInstalled, RETRY_TRIES, handle_adb_error, - possible_reasons, retry_sleep) + handle_unknown_host_service, possible_reasons, retry_sleep) from module.exception import RequestHumanTakeover from module.logger import logger @@ -50,6 +50,10 @@ def retry(func): if handle_adb_error(e): def init(): self.adb_reconnect() + elif handle_unknown_host_service(e): + def init(): + self.adb_start_server() + self.adb_reconnect() else: break # RuntimeError: USB device 127.0.0.1:5555 is offline @@ -121,7 +125,7 @@ class Uiautomator2(Connection): if image is None: raise ImageTruncated('Empty image after cv2.imdecode') - image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB, dst=image) + cv2.cvtColor(image, cv2.COLOR_BGR2RGB, dst=image) if image is None: raise ImageTruncated('Empty image after cv2.cvtColor') diff --git a/module/device/method/utils.py b/module/device/method/utils.py index 18aceef..17b1630 100644 --- a/module/device/method/utils.py +++ b/module/device/method/utils.py @@ -238,12 +238,6 @@ def handle_adb_error(e): # Raised by uiautomator2 when current adb service is killed by another version of adb service. logger.error(e) return True - elif 'unknown host service' in text: - # AdbError(unknown host service) - # Another version of ADB service started, current ADB service has been killed. - # Usually because user opened a Chinese emulator, which uses ADB from the Stone Age. - logger.error(e) - return True else: # AdbError() logger.exception(e) @@ -255,6 +249,25 @@ def handle_adb_error(e): return False +def handle_unknown_host_service(e): + """ + Args: + e (Exception): + + Returns: + bool: If should retry + """ + text = str(e) + if 'unknown host service' in text: + # AdbError(unknown host service) + # Another version of ADB service started, current ADB service has been killed. + # Usually because user opened a Chinese emulator, which uses ADB from the Stone Age. + logger.error(e) + return True + else: + return False + + def get_serial_pair(serial): """ Args: diff --git a/module/device/method/wsa.py b/module/device/method/wsa.py index 56f0ea2..6ae1c60 100644 --- a/module/device/method/wsa.py +++ b/module/device/method/wsa.py @@ -4,8 +4,8 @@ from functools import wraps from adbutils.errors import AdbError from module.device.connection import Connection -from module.device.method.utils import (RETRY_TRIES, retry_sleep, - handle_adb_error, PackageNotInstalled) +from module.device.method.utils import (PackageNotInstalled, RETRY_TRIES, handle_adb_error, handle_unknown_host_service, + retry_sleep) from module.exception import RequestHumanTakeover from module.logger import logger @@ -38,6 +38,10 @@ def retry(func): if handle_adb_error(e): def init(): self.adb_reconnect() + elif handle_unknown_host_service(e): + def init(): + self.adb_start_server() + self.adb_reconnect() else: break # Package not installed