#include "AdbUtils.h" #include #include #include #include #include #include #include AdbUtils::AdbUtils(QObject *parent) : QObject(parent) { } QString AdbUtils::adbPath() { #ifdef Q_OS_WIN QString path = QCoreApplication::applicationDirPath() + "/tools/adb/windows/adb.exe"; #else QString path = QCoreApplication::applicationDirPath() + "/tools/adb/macos/adb"; #endif static bool checked = false; if (!checked) { checked = true; QFileInfo fi(path); if (!fi.exists()) { qWarning() << "[AdbUtils] adb not found at:" << path; } else if (!fi.isExecutable()) { qWarning() << "[AdbUtils] adb is not executable:" << path; } else { qDebug() << "[AdbUtils] adb path:" << path; } } return path; } QString AdbUtils::adbDir() { return QFileInfo(adbPath()).absolutePath(); } bool AdbUtils::startProcess(const QString &program, const QStringList &args, QString *stdOut, QString *stdErr) { QProcess process; process.setWorkingDirectory(adbDir()); process.start(program, args); if (!process.waitForStarted(5000)) { qWarning() << "[AdbUtils] Failed to start process:" << program << args << "error:" << process.errorString(); if (stdErr) { *stdErr = process.errorString(); } return false; } if (!process.waitForFinished(30000) || process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) { if (stdErr) { *stdErr = QString::fromUtf8(process.readAllStandardError()); } qWarning() << "[AdbUtils] Process failed:" << program << args << "exitCode:" << process.exitCode() << "stderr:" << (stdErr ? *stdErr : QString::fromUtf8(process.readAllStandardError())); return false; } if (stdOut) { *stdOut = QString::fromUtf8(process.readAllStandardOutput()); } return true; } // adb shell dumpsys package ru.rshb.dbo | grep permission // TODO доделать обработку пермишинов // adb shell pm revoke ru.rshb.dbo android.permission.WRITE_CONTACTS; adb shell pm revoke ru.rshb.dbo android.permission.READ_CONTACTS; adb shell pm revoke ru.rshb.dbo android.permission.ACCESS_COARSE_LOCATION; adb shell pm revoke ru.rshb.dbo android.permission.ACCESS_FINE_LOCATION // adb shell cmd appops set ru.rshb.dbo POST_NOTIFICATION ignore /** * Возвращает список найденных устройств в виде пар (ID устройства и Статус) * @return [(id, status), ...] */ QList > AdbUtils::getListDevices() { const QString adb = adbPath(); const QString dir = adbDir(); // Убиваем чужой adb-сервер (возможно другой версии) и запускаем свой, // чтобы избежать конфликта версий (например, scrcpy adb 35 vs bundled adb 36) static bool serverRestarted = false; if (!serverRestarted) { serverRestarted = true; QProcess kill; kill.setWorkingDirectory(dir); kill.start(adb, {"kill-server"}); kill.waitForFinished(5000); qDebug() << "[AdbUtils] Killed existing adb server"; QProcess start; start.setWorkingDirectory(dir); start.start(adb, {"start-server"}); start.waitForFinished(10000); qDebug() << "[AdbUtils] Started own adb server"; } QProcess process; process.setWorkingDirectory(dir); process.start(adb, {"devices"}); if (!process.waitForStarted(5000)) { qWarning() << "[AdbUtils] Failed to start adb devices:" << process.errorString(); return {}; } process.waitForFinished(10000); const QString output = process.readAllStandardOutput(); QList > devices; const QStringList lines = output.split('\n'); for (int i = 1; i < lines.size(); ++i) { QString line = lines.at(i).trimmed(); if (line.isEmpty()) { continue; } /** * device — устройство готово к использованию. * offline — устройство подключено, но не может быть использовано. * unauthorized — устройство не авторизовано для работы с ADB (нужно подтвердить запрос на подключение на самом устройстве). * recovery — устройство в режиме восстановления. */ if (QStringList parts = line.split('\t'); parts.size() == 2) { const QString &id = parts.at(0); const QString &status = parts.at(1); devices.append(qMakePair(id, status)); } } return devices; } QString AdbUtils::tryToGetRunningAppPid(const QString &deviceId, const QString &packageName) { QProcess process; process.setWorkingDirectory(adbDir()); process.start(adbPath(), { "-s", deviceId, "shell", "pidof", packageName }); if (!process.waitForStarted(5000)) { qWarning() << "[AdbUtils] Failed to start adb pidof:" << process.errorString(); return {}; } process.waitForFinished(); QString pid = process.readAllStandardOutput(); return pid.replace("\n", ""); } bool AdbUtils::tryToStartApplication(const QString &deviceId, const QString &packageName) { QProcess process; process.setWorkingDirectory(adbDir()); process.start(adbPath(), { "-s", deviceId, "shell", "monkey", "-p", packageName, "-c", "android.intent.category.LAUNCHER", "1" }); if (!process.waitForStarted(5000)) { qWarning() << "[AdbUtils] Failed to start adb monkey:" << process.errorString(); return false; } process.waitForFinished(); const QString output = process.readAllStandardOutput(); // Если процесс запустился, там будет такая строка. // Если нет, то: "** No activities found to run, monkey aborted." if (output.contains("Events injected: 1")) { return true; } // Тут надо в логи писать, почему не стартует qDebug() << "Start app output:" << output; return false; } bool AdbUtils::tryToKillApplication(const QString &deviceId, const QString &packageName) { QProcess process; process.setWorkingDirectory(adbDir()); process.start(adbPath(), { "-s", deviceId, "shell", "am", "force-stop", packageName }); if (!process.waitForStarted(5000)) { qWarning() << "[AdbUtils] Failed to start adb force-stop:" << process.errorString(); return false; } process.waitForFinished(); return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0; } // idx - временный лог // adb -s 6edc4a47 shell 'uiautomator dump /sdcard/uidump.xml' && adb -s 6edc4a47 pull /sdcard/uidump.xml step_1.xml && adb -s 6edc4a47 shell 'rm /sdcard/uidump.xml' QString AdbUtils::getScreenDumpAsXml(const QString &deviceId, int idx, bool screenshot) { const QString adb = adbPath(); for (int retry = 0; retry < 3; ++retry) { // 1. Dump UI hierarchy (retry up to 3 times — uiautomator may be killed by Android) bool dumpOk = false; for (int attempt = 0; attempt < 3; ++attempt) { if (startProcess(adb, {"-s", deviceId, "shell", "uiautomator", "dump", "/sdcard/uidump.xml"})) { dumpOk = true; break; } qWarning() << "[AdbUtils] uiautomator dump failed, attempt" << (attempt + 1) << "of 3"; QThread::msleep(1000); } if (!dumpOk) { qWarning() << "Failed to dump UI hierarchy after 3 attempts."; return {}; } // 2. Optionally pull and save the XML file if (screenshot) { if (!startProcess(adb, {"-s", deviceId, "pull", "/sdcard/uidump.xml", QString("step_%1.xml").arg(idx)})) { qWarning() << "Failed to pull UIDump XML."; return {}; } } // 3. Read the XML content QString xmlOut; if (!startProcess(adb, {"-s", deviceId, "shell", "cat", "/sdcard/uidump.xml"}, &xmlOut)) { qWarning() << "Failed to read UIDump XML."; return {}; } // 4. Remove temp XML file startProcess(adb, {"-s", deviceId, "shell", "rm", "/sdcard/uidump.xml"}); // 5. Проверяем что XML не пустой, достаточного размера и содержит непустые UI-элементы static const QRegularExpression reNonEmpty(R"((text|content-desc)="[^"]+")"); const bool hasContent = reNonEmpty.match(xmlOut).hasMatch(); if (xmlOut.trimmed().isEmpty() || !xmlOut.contains(" 0) args << QString::number(durationMs); QProcess process; process.setWorkingDirectory(adbDir()); process.start(adbPath(), args); if (!process.waitForStarted(5000)) { qWarning() << "[AdbUtils] Failed to start adb swipe:" << process.errorString(); return false; } process.waitForFinished(); return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0; } bool AdbUtils::inputText(const QString &deviceId, const QString &text) { QProcess process; process.setWorkingDirectory(adbDir()); process.start(adbPath(), { "-s", deviceId, "shell", "input", "text", text }); if (!process.waitForStarted(5000)) { qWarning() << "[AdbUtils] Failed to start adb input text:" << process.errorString(); return false; } process.waitForFinished(); return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0; } bool AdbUtils::hideKeyboard(const QString &deviceId) { QProcess process; process.setWorkingDirectory(adbDir()); process.start(adbPath(), { "-s", deviceId, "shell", "input", "keyevent", "111" }); if (!process.waitForStarted(5000)) { qWarning() << "[AdbUtils] Failed to start adb keyevent:" << process.errorString(); return false; } process.waitForFinished(); return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0; } bool AdbUtils::goBack(const QString &deviceId) { QProcess process; process.setWorkingDirectory(adbDir()); process.start(adbPath(), { "-s", deviceId, "shell", "input", "keyevent", "4" }); if (!process.waitForStarted(5000)) { qWarning() << "[AdbUtils] Failed to start adb keyevent:" << process.errorString(); return false; } process.waitForFinished(); return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0; } QSet AdbUtils::getListApps(const QString &deviceId) { QProcess process; process.setWorkingDirectory(adbDir()); process.start(adbPath(), { "-s", deviceId, "shell", "pm list", "packages", "-3" }); if (!process.waitForStarted(5000)) { qWarning() << "[AdbUtils] Failed to start adb pm list:" << process.errorString(); return {}; } process.waitForFinished(); const QString output = process.readAllStandardOutput(); QSet apps; const QStringList lines = output.split("\n"); for (int i = 0; i < lines.size(); ++i) { QString line = lines.at(i).trimmed().replace("package:", ""); if (line.isEmpty()) { continue; } apps.insert(line); } return apps; } QString AdbUtils::getDeviceTimezone(const QString &deviceId) { QString out; if (!startProcess(adbPath(), {"-s", deviceId, "shell", "getprop", "persist.sys.timezone"}, &out)) { return {}; } return out.trimmed(); } void AdbUtils::unlockScreen(const QString &deviceId, const int screenWidth, const int screenHeight) { const QString adb = adbPath(); // Проверяем, включён ли экран (mWakefulness=Awake) QString powerState; startProcess(adb, {"-s", deviceId, "shell", "dumpsys", "power"}, &powerState); const bool isAwake = powerState.contains("mWakefulness=Awake"); if (!isAwake) { // Включаем экран startProcess(adb, {"-s", deviceId, "shell", "input", "keyevent", "KEYCODE_WAKEUP"}); QThread::msleep(500); qDebug() << "[AdbUtils] unlockScreen: woke up device" << deviceId; } // Проверяем, залочен ли экран (mDreamingSleepToken / mShowingLockscreen) QString wmState; startProcess(adb, {"-s", deviceId, "shell", "dumpsys", "window"}, &wmState); const bool isLocked = wmState.contains("mDreamingSleepToken") || wmState.contains("mShowingLockscreen=true") || wmState.contains("StatusBar"); if (isLocked) { // Свайп вверх для разблокировки const int x = screenWidth / 2; makeSwipe(deviceId, x, screenHeight * 3 / 4, x, screenHeight / 4); QThread::msleep(500); qDebug() << "[AdbUtils] unlockScreen: swiped to unlock" << deviceId; } }