1
0
forked from BRT/arc
arc/android/adb/AdbUtils.cpp
slava 6988794657 Refactor ADB utilities to use dynamic adb paths and streamline processes.
Updated all ADB operations to use the dynamically determined adb path for better platform compatibility. Consolidated redundant process-handling code using a new `startProcess` helper method to simplify and standardize command execution. Enhanced `getScreenDumpAsXml` and related functions to improve modularity and maintainability.
2025-06-11 18:13:22 +07:00

325 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "AdbUtils.h"
#include <QProcess>
#include <qDebug>
#include <QString>
#include <QCoreApplication>
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<QPair<QString, QString> > AdbUtils::getListDevices() {
QProcess process;
process.start(adbPath(), {"devices"});
process.waitForFinished();
const QString output = process.readAllStandardOutput();
QList<QPair<QString, QString> > 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("<?xml"); start != -1) {
xmlBytes = xmlBytes.mid(start);
}
return QString::fromUtf8(xmlBytes);
}
bool AdbUtils::takeScreenshot(const QString &deviceId, const QString &name) {
const QString adb = adbPath();
QStringList commands = {
QString("%1 -s %2 shell screencap -p /sdcard/screen.png").arg(adb, deviceId),
QString("%1 -s %2 pull /sdcard/screen.png %3.png").arg(adb, deviceId, name),
QString("%1 -s %2 shell rm /sdcard/screen.png").arg(adb, deviceId)
};
for (const QString &cmd: commands) {
QString stderr;
if (!startProcess(cmd, nullptr, &stderr)) {
qWarning() << "ADB command failed:" << cmd;
qWarning() << "Error:" << stderr;
return false;
}
}
return true;
}
bool AdbUtils::takeXmlDump(const QString &deviceId, const QString &name) {
const QString adb = adbPath();
QStringList commands = {
QString("%1 -s %2 shell uiautomator dump /sdcard/uidump.xml").arg(adb, deviceId),
QString("%1 -s %2 pull /sdcard/uidump.xml %3.xml").arg(adb, deviceId, name),
QString("%1 -s %2 shell cat /sdcard/uidump.xml").arg(adb, deviceId),
QString("%1 -s %2 shell rm /sdcard/uidump.xml").arg(adb, deviceId)
};
for (const QString &cmd: commands) {
QString stderr;
if (!startProcess(cmd, nullptr, &stderr)) {
qWarning() << "ADB command failed:" << cmd;
qWarning() << "Error:" << stderr;
return false;
}
}
return true;
}
bool AdbUtils::makeTap(const QString &deviceId, const int x, const int y) {
QProcess process;
process.start(adbPath(), {
"-s", deviceId,
"shell", "input",
"tap", QString::number(x), QString::number(y)
});
process.waitForFinished();
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
}
bool AdbUtils::enableAdbIMEKeyboard(const QString &deviceId) {
const QString adb = adbPath();
QStringList commands = {
QString("%1 -s %2 shell ime enable com.android.adbkeyboard/.AdbIME").arg(adb, deviceId),
QString("%1 -s %2 shell ime set com.android.adbkeyboard/.AdbIME").arg(adb, deviceId)
};
for (const QString &cmd: commands) {
QString stderr;
if (!startProcess(cmd, nullptr, &stderr)) {
qWarning() << "ADB command failed:" << cmd;
qWarning() << "Error:" << stderr;
return false;
}
}
return true;
}
bool AdbUtils::inputAdbIMEText(const QString &deviceId, const QString &text) {
QProcess process;
QString command = QString(
"%1 -s %2 shell am broadcast -a ADB_INPUT_TEXT --es msg '%3'"
).arg(adbPath(), deviceId, text);
process.start(command);
process.waitForFinished();
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
}
bool AdbUtils::disableAdbIMEKeyboard(const QString &deviceId) {
QProcess process;
QString command = QString(
"%1 -s %2 shell ime disable com.android.adbkeyboard/.AdbIME"
).arg(adbPath(), deviceId);
process.start(command);
process.waitForFinished();
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
}
bool AdbUtils::makeSwipe(const QString &deviceId, const int x1, const int y1, const int x2, const int y2) {
QProcess process;
process.start(
adbPath(),
{
"-s", deviceId,
"shell", "input",
"swipe", QString("%1 %2 %3 %4").arg(x1).arg(y1).arg(x2).arg(y2)
}
);
process.waitForFinished();
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
}
bool AdbUtils::inputText(const QString &deviceId, const QString &text) {
QProcess process;
process.start(adbPath(), {
"-s", deviceId,
"shell", "input",
"text", text
});
process.waitForFinished();
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
}
bool AdbUtils::goBack(const QString &deviceId) {
QProcess process;
process.start(adbPath(), {
"-s", deviceId,
"shell", "input",
"keyevent", "4"
});
process.waitForFinished();
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
}
QSet<QString> 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<QString> 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;
}