#pragma once #include #include class AdbUtils final : public QObject { Q_OBJECT public: explicit AdbUtils(QObject *parent = nullptr); static QString adbPath(); static QList > getListDevices(); // --- Wi-Fi подключение (wireless debugging) --- // Спаривание по коду: `adb pair IP:port code`. true при "Successfully paired". static bool adbPair(const QString &ip, int port, const QString &code); // `adb connect IP:port`. Возвращает рабочий "IP:port" при успехе, иначе пусто // (adb может вернуть exit 0 и при неудаче — судим по тексту вывода). static QString adbConnect(const QString &ip, int port); // `adb disconnect ` (best-effort). static bool adbDisconnect(const QString &endpoint); // Поиск connect-эндпоинта устройства по IP через `adb mdns services` // (_adb-tls-connect._tcp). Возвращает "IP:port" или пусто. static QString mdnsFindConnect(const QString &ip); // android_id устройства (`settings get secure android_id`) — стабильная личность. // Пусто при ошибке/таймауте. static QString getAndroidId(const QString &deviceId); // Fallback-поиск connect-порта, когда он сменился (тоггл/ребут wireless debugging), // а mDNS в сети не работает: параллельно сканирует IP телефона на открытые порты и // пробует `adb connect` (pairing сохранён → код не нужен). Возвращает рабочий // "IP:port" или пусто. Тяжёлая операция — звать только как fallback. static QString scanFindConnect(const QString &ip, int portStart = 30000, int portEnd = 50000); static QString tryToGetRunningAppPid(const QString &deviceId, const QString &packageName); static bool tryToStartApplication(const QString &deviceId, const QString &packageName); static bool tryToKillApplication(const QString &deviceId, const QString &packageName); static QString getScreenDumpAsXml(const QString &deviceId, int idx, bool screenshot = false); // Сырой дамп без фильтра "useful UI" — для ручного превью оператора: // отдаёт что есть на экране, даже если это PIN-пад / сплэш / WebView-shell. static QString getRawScreenDumpAsXml(const QString &deviceId); static bool takeScreenshot(const QString &deviceId, const QString &name); // IP телефона в Wi-Fi (кэш по serial с TTL). Для IP:port-серийника берётся из // серийника, иначе `adb shell ip route get 1.1.1.1` → src. Пусто, если не в Wi-Fi. // Используется и для http-скриншота, и для сохранения IP в БД (wifi_connections). static QString resolvePhoneIp(const QString &deviceId); // Текущий режим скриншотов устройства: "http" | "exec-out (fallback)" | "" (ещё не // определялся). Для индикации в UI. Ключ — adb-серийник. static QString screenshotMode(const QString &deviceId); // Захватывает скриншот напрямую в память (PNG-байты), без временного файла. // Выбирает стратегию по config.ini [screenshot] transport: http (через APK // arc-shot, мимо adb) с fallback на exec-out, либо сразу exec-out. static QByteArray captureScreenshotBytes(const QString &deviceId); // Скриншот через APK arc-shot: adb screencap в файл на /sdcard (дёшево, без // передачи байтов через adb) + HTTP-GET файла напрямую с телефона по его // Wi-Fi IP. Разгружает adb. Пусто при недоступности (нет APK/сети) — вызывающий // откатывается на exec-out. Возвращает PNG-байты. static QByteArray captureScreenshotHttp(const QString &deviceId); static bool takeXmlDump(const QString &deviceId, const QString &name); static bool makeTap(const QString &deviceId, int x, int y); static bool enableAdbIMEKeyboard(const QString &deviceId); static bool inputAdbIMEText(const QString &deviceId, const QString &text); static bool disableAdbIMEKeyboard(const QString &deviceId); static bool makeSwipe(const QString &deviceId, int x1, int y1, int x2, int y2); static bool makeSwipe(const QString &deviceId, int x1, int y1, int x2, int y2, int durationMs); static bool inputText(const QString &deviceId, const QString &text); static bool hideKeyboard(const QString &deviceId); static bool goBack(const QString &deviceId); static void unlockScreen(const QString &deviceId, int screenWidth, int screenHeight); static QSet getListApps(const QString &deviceId); // Возвращает таймзону устройства (напр. "Asia/Bangkok"), либо пустую строку при ошибке static QString getDeviceTimezone(const QString &deviceId); // Возвращает versionName установленного пакета (напр. "19.17.0"), либо пустую строку при ошибке/отсутствии static QString getAppVersion(const QString &deviceId, const QString &packageName); // Возвращает пакет приложения на переднем плане (foreground), либо пустую строку при ошибке static QString getForegroundPackage(const QString &deviceId); // Проверяет реальную ориентацию экрана (dumpsys input → SurfaceOrientation). // Если экран в горизонтальной ориентации (landscape, 90°/270°), принудительно // ставит вертикальную: выключает автоповорот и выставляет user_rotation=0. // Best-effort: при ошибке adb просто ничего не делает. Возвращает true, если // экран в портрете (изначально или после форсирования). static bool ensurePortraitOrientation(const QString &deviceId); private: static QString adbDir(); static bool startProcess(const QString &program, const QStringList &args, QString *stdOut = nullptr, QString *stdErr = nullptr); // Старый способ: exec-out screencap -p (стримит PNG через adb). Fallback для http. static QByteArray captureScreenshotExecOut(const QString &deviceId); // Синхронный HTTP/1.0 GET по QTcpSocket (без event-loop, безопасно из worker-потоков). // Возвращает тело ответа при 200, иначе пусто. static QByteArray httpGet(const QString &ip, int port, const QString &path, int timeoutMs); // Поднимает foreground-сервис arc-shot (ставит APK, если удалён). Дёргается из // http-fail ветки, т.е. не чаще раза в бэкофф-окно. static void kickArcShot(const QString &deviceId); };