Improve package state probing and logging in AdbUtils and enhance "Other Bank" button handling in PayByPhoneScript.
This commit is contained in:
parent
ff4a468b35
commit
7e5f90a450
@ -152,7 +152,71 @@ QString AdbUtils::tryToGetRunningAppPid(const QString &deviceId, const QString &
|
|||||||
return pid.replace("\n", "");
|
return pid.replace("\n", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Логирует длинный вывод построчно с префиксом, иначе Qt-логгер режет строку.
|
||||||
|
static void logMultiline(const char *tag, const QString &text) {
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
qDebug().noquote() << tag << "(empty)";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const QStringList lines = text.split(QRegularExpression("\r?\n"));
|
||||||
|
for (const QString &line : lines) {
|
||||||
|
if (!line.isEmpty()) qDebug().noquote() << tag << line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Прогоняет adb-команду и возвращает stdout (stderr игнорируется — этим функциям важен только stdout).
|
||||||
|
static QString adbShellStdout(const QStringList &args, int timeoutMs = 10000) {
|
||||||
|
QProcess p;
|
||||||
|
p.setWorkingDirectory(QFileInfo(AdbUtils::adbPath()).absolutePath());
|
||||||
|
p.start(AdbUtils::adbPath(), args);
|
||||||
|
if (!p.waitForStarted(5000)) return {};
|
||||||
|
p.waitForFinished(timeoutMs);
|
||||||
|
return QString::fromUtf8(p.readAllStandardOutput());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Возвращает {launcherActivity, summary}. launcherActivity вида "pkg/.MainActivity" или пустая строка.
|
||||||
|
static QPair<QString, QString> probePackageState(const QString &deviceId, const QString &packageName) {
|
||||||
|
const QString resolveOut = adbShellStdout(
|
||||||
|
{"-s", deviceId, "shell", "cmd", "package", "resolve-activity", "--brief", packageName});
|
||||||
|
QString launcher;
|
||||||
|
// Формат: первая строка — приоритет, вторая — "pkg/activity".
|
||||||
|
for (const QString &raw : resolveOut.split(QRegularExpression("\r?\n"))) {
|
||||||
|
const QString line = raw.trimmed();
|
||||||
|
if (line.contains('/') && line.startsWith(packageName + "/")) {
|
||||||
|
launcher = line;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString dump = adbShellStdout(
|
||||||
|
{"-s", deviceId, "shell", "dumpsys", "package", packageName}, 15000);
|
||||||
|
|
||||||
|
auto pickFlag = [&dump](const QString &key) -> QString {
|
||||||
|
const QRegularExpression re(key + "=(\\S+)");
|
||||||
|
const auto m = re.match(dump);
|
||||||
|
return m.hasMatch() ? m.captured(1) : QStringLiteral("?");
|
||||||
|
};
|
||||||
|
const bool installed = dump.contains("Package [" + packageName + "]");
|
||||||
|
const QString enabled = pickFlag("enabled");
|
||||||
|
const QString stopped = pickFlag("stopped");
|
||||||
|
const QString hidden = pickFlag("hidden");
|
||||||
|
const bool hasLauncherCategory = dump.contains("android.intent.category.LAUNCHER");
|
||||||
|
|
||||||
|
const QString summary = QString(
|
||||||
|
"installed=%1 enabled=%2 stopped=%3 hidden=%4 hasLauncherCategory=%5 resolveActivity=%6")
|
||||||
|
.arg(installed ? "true" : "false", enabled, stopped, hidden,
|
||||||
|
hasLauncherCategory ? "true" : "false",
|
||||||
|
launcher.isEmpty() ? "(none)" : launcher);
|
||||||
|
return {launcher, summary};
|
||||||
|
}
|
||||||
|
|
||||||
bool AdbUtils::tryToStartApplication(const QString &deviceId, const QString &packageName) {
|
bool AdbUtils::tryToStartApplication(const QString &deviceId, const QString &packageName) {
|
||||||
|
// Pre-flight: состояние пакета. Без этого по логу нельзя отличить
|
||||||
|
// "нет иконки" от "disabled" от "hidden" от "не установлен".
|
||||||
|
const auto [launcherActivity, stateSummary] = probePackageState(deviceId, packageName);
|
||||||
|
qDebug().noquote() << "[AdbUtils::tryToStartApplication] device=" << deviceId
|
||||||
|
<< "pkg=" << packageName << "state:" << stateSummary;
|
||||||
|
|
||||||
QProcess process;
|
QProcess process;
|
||||||
process.setWorkingDirectory(adbDir());
|
process.setWorkingDirectory(adbDir());
|
||||||
process.start(adbPath(), {
|
process.start(adbPath(), {
|
||||||
@ -167,16 +231,47 @@ bool AdbUtils::tryToStartApplication(const QString &deviceId, const QString &pac
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
process.waitForFinished();
|
process.waitForFinished();
|
||||||
const QString output = process.readAllStandardOutput();
|
const QString stdOut = QString::fromUtf8(process.readAllStandardOutput());
|
||||||
|
const QString stdErr = QString::fromUtf8(process.readAllStandardError());
|
||||||
|
const int exitCode = process.exitCode();
|
||||||
|
const QProcess::ExitStatus exitStatus = process.exitStatus();
|
||||||
|
|
||||||
// Если процесс запустился, там будет такая строка.
|
// Полный вывод monkey пишем построчно — Qt-логгер режет длинные QString,
|
||||||
// Если нет, то: "** No activities found to run, monkey aborted."
|
// из-за чего хвост (`Events injected` / `No activities found`) терялся.
|
||||||
if (output.contains("Events injected: 1")) {
|
qDebug().noquote() << "[AdbUtils::tryToStartApplication] monkey exitStatus="
|
||||||
|
<< exitStatus << "exitCode=" << exitCode;
|
||||||
|
logMultiline("[AdbUtils::tryToStartApplication] monkey stdout:", stdOut);
|
||||||
|
logMultiline("[AdbUtils::tryToStartApplication] monkey stderr:", stdErr);
|
||||||
|
|
||||||
|
if (stdOut.contains("Events injected: 1")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Тут надо в логи писать, почему не стартует
|
// Фолбэк: am start с конкретной activity. У него осмысленный stderr и exit code,
|
||||||
qDebug() << "Start app output:" << output;
|
// в отличие от monkey, который при пустом resolve просто молча печатает "No activities found".
|
||||||
|
if (!launcherActivity.isEmpty()) {
|
||||||
|
qDebug().noquote() << "[AdbUtils::tryToStartApplication] monkey не дал Events injected;"
|
||||||
|
<< "пробую am start -n" << launcherActivity;
|
||||||
|
QProcess am;
|
||||||
|
am.setWorkingDirectory(adbDir());
|
||||||
|
am.start(adbPath(),
|
||||||
|
{"-s", deviceId, "shell", "am", "start", "-W", "-n", launcherActivity});
|
||||||
|
am.waitForStarted(5000);
|
||||||
|
am.waitForFinished(15000);
|
||||||
|
const QString amOut = QString::fromUtf8(am.readAllStandardOutput());
|
||||||
|
const QString amErr = QString::fromUtf8(am.readAllStandardError());
|
||||||
|
qDebug().noquote() << "[AdbUtils::tryToStartApplication] am exitCode=" << am.exitCode();
|
||||||
|
logMultiline("[AdbUtils::tryToStartApplication] am stdout:", amOut);
|
||||||
|
logMultiline("[AdbUtils::tryToStartApplication] am stderr:", amErr);
|
||||||
|
// `am start -W` печатает "Status: ok" при успехе.
|
||||||
|
if (am.exitCode() == 0 && amOut.contains("Status: ok")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qWarning() << "[AdbUtils::tryToStartApplication] нет launcher-активити для" << packageName
|
||||||
|
<< "— приложение либо не установлено, либо disabled/hidden, либо без LAUNCHER intent-filter";
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -296,23 +296,39 @@ void PayByPhoneScript::makePayment(
|
|||||||
|
|
||||||
// Экран выбора банка: ищем "Другой банк" (текст) или EditText с hint "Название банка"
|
// Экран выбора банка: ищем "Другой банк" (текст) или EditText с hint "Название банка"
|
||||||
if (!bankSelected) {
|
if (!bankSelected) {
|
||||||
// Вариант 1: кнопка "Другой банк" (content-desc="other-bank-item", text пустой)
|
// Вариант 1: кнопка "Другой банк" (content-desc="other-bank-item", text пустой).
|
||||||
bool foundOtherBank = false;
|
// На устройствах с маленьким экраном кнопка лежит в конце виртуализированного
|
||||||
{
|
// списка банков и приходит с bounds=[0,0][0,0] — Node::isEmpty это не отлавливает
|
||||||
Node node;
|
// (требует все координаты < 0), и тап уходит в (0,0). Скроллим список вниз,
|
||||||
|
// пока bounds не станут реальными, и только тогда тапаем.
|
||||||
|
auto findOtherBank = [&]() -> Node {
|
||||||
for (const Node &n : xmlScreenParser.findAllNodes()) {
|
for (const Node &n : xmlScreenParser.findAllNodes()) {
|
||||||
if (n.contentDesc.contains("other-bank-item")) {
|
if (n.contentDesc.contains("other-bank-item")) return n;
|
||||||
node = n;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!node.isEmpty()) {
|
|
||||||
qDebug() << "[PayByPhone] Tapping 'Другой банк' (other-bank-item)...";
|
|
||||||
AdbUtils::makeTap(deviceId, node.x(), node.y());
|
|
||||||
QThread::msleep(2000);
|
|
||||||
foundOtherBank = true;
|
|
||||||
otherBankClicked = true;
|
|
||||||
}
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
bool foundOtherBank = false;
|
||||||
|
Node node = findOtherBank();
|
||||||
|
for (int scrollAttempt = 0;
|
||||||
|
!node.isEmpty() && (node.width() == 0 || node.height() == 0) && scrollAttempt < 6;
|
||||||
|
++scrollAttempt) {
|
||||||
|
qDebug() << "[PayByPhone] 'Другой банк' has collapsed bounds, scrolling list down, attempt"
|
||||||
|
<< scrollAttempt + 1 << "/ 6";
|
||||||
|
AdbUtils::makeSwipe(deviceId, width / 2, height * 3 / 4, width / 2, height / 4);
|
||||||
|
QThread::msleep(800);
|
||||||
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||||
|
closeGooglePlayBannerIfVisible(deviceId, width, height);
|
||||||
|
node = findOtherBank();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node.isEmpty() && node.width() > 0 && node.height() > 0) {
|
||||||
|
qDebug() << "[PayByPhone] Tapping 'Другой банк' (other-bank-item) at"
|
||||||
|
<< node.x() << "," << node.y();
|
||||||
|
AdbUtils::makeTap(deviceId, node.x(), node.y());
|
||||||
|
QThread::msleep(2000);
|
||||||
|
foundOtherBank = true;
|
||||||
|
otherBankClicked = true;
|
||||||
}
|
}
|
||||||
if (foundOtherBank) continue;
|
if (foundOtherBank) continue;
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user