# -*- coding: utf-8 -*- """ ADB Helper Module 封装 DroidBot 的 ADB 适配器,提供简化的接口供 download_app 模块使用 """ import logging from DroidBot.platforms.android.adapters.adb import ADB from DroidBot.exceptions import ADBException from utils_android.device_config import ensure_android_wireless_connected, get_android_device_serial logger = logging.getLogger(__name__) class SimpleDevice: """简化的设备对象,用于初始化 ADB ADB 类需要一个 device 参数,但 download_app 模块不需要完整的设备对象。 这个类提供最小化的接口满足 ADB 初始化需求。 """ def __init__(self, serial=None): serial = serial or get_android_device_serial() if serial: ensure_android_wireless_connected(serial=serial) # 如果没有指定 serial,自动获取第一个可用设备 if serial is None: from DroidBot.platforms.android.utils import get_available_devices all_devices = get_available_devices() if len(all_devices) == 0: logger.error("ERROR: No device connected.") raise ADBException("No device connected") serial = all_devices[0] logger.info(f"Auto-detected device: {serial}") self.serial = serial self.logger = logger class ADBHelper: """ADB 辅助类,封装常用的 ADB 命令 提供统一的 ADB 接口,确保所有 ADB 命令失败时都能抛出 ADBException。 这样主进程 batch_run.py 可以捕获并处理 ADB 相关错误。 使用示例: helper = ADBHelper() output = helper.shell("pm list packages") helper.install("/path/to/app.apk") """ def __init__(self, serial=None): """初始化 ADB Helper Args: serial: 设备序列号,None 表示使用默认设备 """ device = SimpleDevice(serial) self.adb = ADB(device) def shell(self, cmd): """执行 adb shell 命令 Args: cmd: shell 命令字符串,例如 "pm list packages" Returns: 命令输出字符串 Raises: ADBException: ADB 命令执行失败时抛出 """ return self.adb.shell(cmd) def run_cmd(self, args): """执行 adb 命令 Args: args: 命令参数列表或字符串,例如 ['install', '-r', 'app.apk'] Returns: 命令输出字符串 Raises: ADBException: ADB 命令执行失败时抛出 """ return self.adb.run_cmd(args) def install(self, apk_path): """安装单个 APK Args: apk_path: APK 文件路径 Returns: 命令输出字符串 Raises: ADBException: 安装失败时抛出 """ import os, shutil, tempfile need_copy = apk_path.startswith('\\\\') if need_copy: tmp_dir = tempfile.mkdtemp(prefix='apk_install_') local_path = os.path.join(tmp_dir, os.path.basename(apk_path)) shutil.copy2(apk_path, local_path) install_path = local_path else: tmp_dir = None install_path = apk_path try: output = self.adb.run_cmd(['install', '-r', install_path]) if 'Success' not in output: detail = getattr(self.adb, 'last_error', '') or '' raise ADBException(f"Install failed: {detail}") return output finally: if tmp_dir: shutil.rmtree(tmp_dir, ignore_errors=True) def install_multiple(self, apk_paths): """安装多个 APK(split APKs) Args: apk_paths: APK 文件路径列表 Returns: 命令输出字符串 Raises: ADBException: 安装失败时抛出 """ import os, shutil, tempfile need_copy = any(p.startswith('\\\\') for p in apk_paths) if need_copy: tmp_dir = tempfile.mkdtemp(prefix='apk_install_') local_paths = [] for p in apk_paths: dst = os.path.join(tmp_dir, os.path.basename(p)) shutil.copy2(p, dst) local_paths.append(dst) apk_paths = local_paths else: tmp_dir = None try: args = ['install-multiple', '-r'] + apk_paths output = self.adb.run_cmd(args) if 'Success' not in output: detail = getattr(self.adb, 'last_error', '') or '' raise ADBException(f"Install-multiple failed: {detail}") return output finally: if tmp_dir: shutil.rmtree(tmp_dir, ignore_errors=True) def uninstall(self, package_name): """卸载应用 Args: package_name: 应用包名 Returns: bool: True 表示卸载成功,False 表示命令执行了但卸载失败 Raises: ADBException: ADB 连接异常时抛出 """ output = self.adb.run_cmd(['uninstall', package_name]) return 'Success' in output