#include "AdbUtils.h" #include #include #include #include AdbUtils::AdbUtils(QObject *parent) : QObject(parent) { } QString AdbUtils::adbPath() { #ifdef Q_OS_WIN return QCoreApplication::applicationDirPath() + "/tools/adb/windows/adb.exe"; #else return QCoreApplication::applicationDirPath() + "/tools/adb/macos/adb"; #endif } bool AdbUtils::startProcess(const QString &cmd, QString *stdOut = nullptr, QString *stdErr = nullptr) { QProcess process; #ifdef Q_OS_WIN process.start("cmd.exe", {"/C", cmd}); #else process.start("sh", {"-c", cmd}); #endif if (!process.waitForFinished() || process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) { if (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() { QProcess process; process.start(adbPath(), {"devices"}); process.waitForFinished(); 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.start(adbPath(), { "-s", deviceId, "shell", "pidof", packageName }); process.waitForFinished(); QString pid = process.readAllStandardOutput(); return pid.replace("\n", ""); } bool AdbUtils::tryToStartApplication(const QString &deviceId, const QString &packageName) { QProcess process; process.start(adbPath(), { "-s", deviceId, "shell", "monkey", "-p", packageName, "-c", "android.intent.category.LAUNCHER", "1" }); 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.start(adbPath(), { "-s", deviceId, "shell", "am", "force-stop", packageName }); 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(); QString output; // 1. Dump UI hierarchy QString dumpCmd = QString("%1 -s %2 shell uiautomator dump /sdcard/uidump.xml").arg(adb, deviceId); if (!startProcess(dumpCmd)) { qWarning() << "Failed to dump UI hierarchy."; return {}; } // 2. Optionally pull and save the XML file if (screenshot) { QString pullCmd = QString("%1 -s %2 pull /sdcard/uidump.xml step_%3.xml").arg(adb, deviceId).arg(idx); if (!startProcess(pullCmd)) { qWarning() << "Failed to pull UIDump XML."; return {}; } } // 3. Read the XML content QString catCmd = QString("%1 -s %2 shell cat /sdcard/uidump.xml").arg(adb, deviceId); QString xmlOut; if (!startProcess(catCmd, &xmlOut)) { qWarning() << "Failed to read UIDump XML."; return {}; } // 4. Remove temp XML file QString rmCmd = QString("%1 -s %2 shell rm /sdcard/uidump.xml").arg(adb, deviceId); startProcess(rmCmd); // неважно, успешна ли команда // 5. Screenshot (if needed) if (screenshot) { QString shotCmds[] = { QString("%1 -s %2 shell screencap -p /sdcard/screen.png").arg(adb, deviceId), QString("%1 -s %2 pull /sdcard/screen.png screen_step_%3.png").arg(adb, deviceId).arg(idx), QString("%1 -s %2 shell rm /sdcard/screen.png").arg(adb, deviceId) }; for (const QString &cmd: shotCmds) { startProcess(cmd); // ошибки игнорируем для скриншота } } // 6. Обрезка к началу XML QByteArray xmlBytes = xmlOut.toUtf8(); if (const int start = xmlBytes.indexOf(" AdbUtils::getListApps(const QString &deviceId) { QProcess process; process.start(adbPath(), { "-s", deviceId, "shell", "pm list", "packages", "-3" }); 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; }