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", "");
|
||||
}
|
||||
|
||||
// Логирует длинный вывод построчно с префиксом, иначе 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) {
|
||||
// Pre-flight: состояние пакета. Без этого по логу нельзя отличить
|
||||
// "нет иконки" от "disabled" от "hidden" от "не установлен".
|
||||
const auto [launcherActivity, stateSummary] = probePackageState(deviceId, packageName);
|
||||
qDebug().noquote() << "[AdbUtils::tryToStartApplication] device=" << deviceId
|
||||
<< "pkg=" << packageName << "state:" << stateSummary;
|
||||
|
||||
QProcess process;
|
||||
process.setWorkingDirectory(adbDir());
|
||||
process.start(adbPath(), {
|
||||
@ -167,16 +231,47 @@ bool AdbUtils::tryToStartApplication(const QString &deviceId, const QString &pac
|
||||
return false;
|
||||
}
|
||||
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();
|
||||
|
||||
// Если процесс запустился, там будет такая строка.
|
||||
// Если нет, то: "** No activities found to run, monkey aborted."
|
||||
if (output.contains("Events injected: 1")) {
|
||||
// Полный вывод monkey пишем построчно — Qt-логгер режет длинные QString,
|
||||
// из-за чего хвост (`Events injected` / `No activities found`) терялся.
|
||||
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;
|
||||
}
|
||||
|
||||
// Тут надо в логи писать, почему не стартует
|
||||
qDebug() << "Start app output:" << output;
|
||||
// Фолбэк: am start с конкретной activity. У него осмысленный stderr и exit code,
|
||||
// в отличие от 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;
|
||||
}
|
||||
|
||||
|
||||
@ -296,23 +296,39 @@ void PayByPhoneScript::makePayment(
|
||||
|
||||
// Экран выбора банка: ищем "Другой банк" (текст) или EditText с hint "Название банка"
|
||||
if (!bankSelected) {
|
||||
// Вариант 1: кнопка "Другой банк" (content-desc="other-bank-item", text пустой)
|
||||
bool foundOtherBank = false;
|
||||
{
|
||||
Node node;
|
||||
// Вариант 1: кнопка "Другой банк" (content-desc="other-bank-item", text пустой).
|
||||
// На устройствах с маленьким экраном кнопка лежит в конце виртуализированного
|
||||
// списка банков и приходит с bounds=[0,0][0,0] — Node::isEmpty это не отлавливает
|
||||
// (требует все координаты < 0), и тап уходит в (0,0). Скроллим список вниз,
|
||||
// пока bounds не станут реальными, и только тогда тапаем.
|
||||
auto findOtherBank = [&]() -> Node {
|
||||
for (const Node &n : xmlScreenParser.findAllNodes()) {
|
||||
if (n.contentDesc.contains("other-bank-item")) {
|
||||
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;
|
||||
if (n.contentDesc.contains("other-bank-item")) return n;
|
||||
}
|
||||
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;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user