1
0
forked from BRT/arc
arc/android/adb/AdbUtils.cpp
slava b8b2b38f8a Add ADB utilities with platform-specific support
Introduce ADB utilities for executing commands like taking XML dumps from connected devices. Refactor the `takeXmlDump` method to dynamically determine the ADB path based on the platform. Include ADB tools and necessary files for both Windows and macOS in the build system.
2025-06-11 16:21:13 +07:00

293 lines
11 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/adb.exe";
#else
return QCoreApplication::applicationDirPath() + "/tools/adb/macos/adb";
#endif
}
// 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("adb", {"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("adb", {
"-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("adb", {
"-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("adb", {
"-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, const int idx, const bool screenshot) {
// TODO убрать логирование в xml файл
// "adb -s %1 pull /sdcard/uidump.xml step_%2.xml && "
QProcess process;
QString xml;
if (screenshot) {
xml = QString(
"adb -s %1 shell 'uiautomator dump /sdcard/uidump.xml' && "
"adb -s %1 pull /sdcard/uidump.xml step_%2.xml && "
"adb -s %1 shell 'cat /sdcard/uidump.xml' && "
"adb -s %1 shell 'rm /sdcard/uidump.xml'"
).arg(deviceId, QString::number(idx));
} else {
xml = QString(
"adb -s %1 shell 'uiautomator dump /sdcard/uidump.xml' && "
"adb -s %1 shell 'cat /sdcard/uidump.xml' && "
"adb -s %1 shell 'rm /sdcard/uidump.xml'"
).arg(deviceId);
}
process.start("sh", {"-c", xml});
process.waitForFinished();
if (screenshot) {
// TODO делаем скрин, убрать потом
// adb -s 6edc4a47 shell 'screencap -p /sdcard/screen.png' && adb -s 6edc4a47 pull /sdcard/screen.png screen_step_1.png && adb -s 6edc4a47 shell 'rm /sdcard/screen.png'
QProcess processScreenshot;
QString screenshot = QString(
"adb -s %1 shell 'screencap -p /sdcard/screen.png' && "
"adb -s %1 pull /sdcard/screen.png screen_step_%2.png && "
"adb -s %1 shell 'rm /sdcard/screen.png'"
).arg(deviceId, QString::number(idx));
processScreenshot.start("sh", {"-c", screenshot});
processScreenshot.waitForFinished();
}
// чтобы убедиться, что мы прочитали без ошибок
if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
return {};
}
QByteArray xmlBytes = process.readAllStandardOutput();
// UI hierchary dumped to: /sdcard/uidump.xml\n
if (const int startIndex = xmlBytes.indexOf("<?xml"); startIndex != -1) {
xmlBytes = xmlBytes.mid(startIndex); // Обрезаем всё до "<?xml"
}
return QString::fromUtf8(xmlBytes);
}
bool AdbUtils::takeScreenshot(const QString &deviceId, const QString &name) {
QProcess process;
QString screenshot = QString(
"adb -s %1 shell 'screencap -p /sdcard/screen.png' && "
"adb -s %1 pull /sdcard/screen.png %2.png && "
"adb -s %1 shell 'rm /sdcard/screen.png'"
).arg(deviceId, name);
process.start("sh", {"-c", screenshot});
process.waitForFinished();
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
}
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) {
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) {
qWarning() << "ADB command failed:" << cmd;
return false;
}
}
return true;
}
bool AdbUtils::makeTap(const QString &deviceId, const int x, const int y) {
QProcess process;
process.start("adb", {
"-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) {
QProcess process;
QString command = QString(
"adb -s %1 shell ime enable com.android.adbkeyboard/.AdbIME "
"&& adb -s %1 shell ime set com.android.adbkeyboard/.AdbIME"
).arg(deviceId);
process.start(command);
process.waitForFinished();
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
}
bool AdbUtils::inputAdbIMEText(const QString &deviceId, const QString &text) {
QProcess process;
QString command = QString(
"adb -s %1 shell am broadcast -a ADB_INPUT_TEXT --es msg '%2'"
).arg(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(
"adb -s %1 shell ime disable com.android.adbkeyboard/.AdbIME"
).arg(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(
"adb",
{
"-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("adb", {
"-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("adb", {
"-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("adb", {
"-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;
}