diff --git a/CMakeLists.txt b/CMakeLists.txt index c4f10bd..b9efdbf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ endif () find_package(Qt6 REQUIRED COMPONENTS Widgets Sql + Xml ) file(GLOB_RECURSE DB_SOURCES @@ -46,23 +47,24 @@ file(GLOB_RECURSE MODELS_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/models/*.h" ) +file(GLOB_RECURSE ANDROID_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/android/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/android/*.h" +) + add_executable(ARCDesktopProject main.cpp ${VIEW_SOURCES} ${SERVICES_SOURCES} ${DB_SOURCES} ${MODELS_SOURCES} - automation_script/adb_fun.cpp - automation_script/adb_fun.h - automation_script/xml_reader.cpp - automation_script/xml_reader.h - automation_script/script_worker.cpp - automation_script/script_worker.h + ${ANDROID_SOURCES} ) if (WIN32) target_link_libraries(ARCDesktopProject Qt6::Widgets + Qt6::Xml Qt6::Sql Tesseract::libtesseract ) @@ -76,6 +78,7 @@ elseif (APPLE) target_link_libraries(ARCDesktopProject Qt6::Widgets Qt6::Sql + Qt6::Xml tesseract ) endif () @@ -85,4 +88,5 @@ target_include_directories(ARCDesktopProject PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/database ${CMAKE_CURRENT_SOURCE_DIR}/services ${CMAKE_CURRENT_SOURCE_DIR}/models + ${CMAKE_CURRENT_SOURCE_DIR}/android ) \ No newline at end of file diff --git a/android/adb/AdbUtils.cpp b/android/adb/AdbUtils.cpp new file mode 100644 index 0000000..38124fa --- /dev/null +++ b/android/adb/AdbUtils.cpp @@ -0,0 +1,207 @@ +#include "AdbUtils.h" +#include +#include +#include + +AdbUtils::AdbUtils(QObject *parent) : QObject(parent) { +} + +// 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("adb", {"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("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) { + // TODO убрать логирование в xml файл + QProcess process; + QString 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)); + process.start("sh", {"-c", xml}); + process.waitForFinished(); + + // 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(" +#include + +class AdbUtils final : public QObject { + Q_OBJECT + +public: + explicit AdbUtils(QObject *parent = nullptr); + + static QList > getListDevices(); + + static QString tryToGetRunningAppPid(const QString &deviceId, const QString &packageName); + + static bool tryToStartApplication(const QString &deviceId, const QString &packageName); + + static bool tryToKillApplication(const QString &deviceId, const QString &packageName); + + static QString getScreenDumpAsXml(const QString &deviceId, const int idx); + + static bool makeTap(const QString &deviceId, int x, int y); + + static bool enableAdbIMEKeyboard(const QString &deviceId); + + static bool inputAdbIMEText(const QString &deviceId, const QString &text); + + static bool disableAdbIMEKeyboard(const QString &deviceId); + + static bool makeSwipe(const QString &deviceId, int x1, int y1, int x2, int y2); + + static bool inputText(const QString &deviceId, const QString &text); + + static bool goBack(const QString &deviceId); +}; diff --git a/android/rshb/HomeScreenScript.cpp b/android/rshb/HomeScreenScript.cpp new file mode 100644 index 0000000..51e5cba --- /dev/null +++ b/android/rshb/HomeScreenScript.cpp @@ -0,0 +1,432 @@ +#include "HomeScreenScript.h" + +#include +#include +#include + +#include "ScreenXmlParser.h" +#include "adb/AdbUtils.h" + + +HomeScreenScript::HomeScreenScript(const int transactionId, QObject *parent) + : QObject(parent), transactionId(transactionId) { +} + +HomeScreenScript::~HomeScreenScript() = default; + +bool HomeScreenScript::goToHomeScreen( + const QString &deviceId, + const QString &packageName, + const QString &pinCode, + const int width, + const int height +) { + // Запустить устройство + if (const QString pid = AdbUtils::tryToGetRunningAppPid(deviceId, packageName); !pid.isEmpty()) { + qDebug() << "Process already running, pid: " << pid; + AdbUtils::getScreenDumpAsXml(deviceId, 1000); + AdbUtils::tryToKillApplication(deviceId, packageName); + } + if (!AdbUtils::tryToStartApplication(deviceId, packageName)) { + qWarning() << "Process has not started"; + return false; + } + + xmlScreenParser.parseAndSaveXml( + AdbUtils::getScreenDumpAsXml(deviceId, 0) + ); + + // Даем минимальное время на запуск + QThread::msleep(4500); + int counter = 0; + int restarted = 0; + int totalCounter = 0; + bool findAndInputPincode = false; + while (true) { + counter++; + totalCounter++; + + // 3 раза уже перезапускали приложение, завершаем попытки + if (restarted > 2) { + return false; + } + + // Если более 30 секунд мы пытаемся перейти на домашнюю, перезапускаем + if (counter > 30) { + AdbUtils::tryToKillApplication(deviceId, packageName); + QThread::msleep(4500); + ++restarted; + counter = 0; + } + + const QString xml = AdbUtils::getScreenDumpAsXml(deviceId, totalCounter + 1); + xmlScreenParser.parseAndSaveXml(xml); + qDebug() << "Created xml+screenshot, idx: " << (totalCounter + 1); + + if (xmlScreenParser.isHomeScreen()) { + qDebug() << "isHomeScreen!!!!!!!"; + return true; + } + + // Проверяем на банеры после пин-кодом + Node closeBannerOrDialogNode = xmlScreenParser.tryToFindBannerOrDialog(width, height); + if (closeBannerOrDialogNode.x() != -1 && closeBannerOrDialogNode.y() != -1) { + qDebug() << "--- tryToFindBannerOrDialog"; + AdbUtils::makeTap(deviceId, closeBannerOrDialogNode.x(), closeBannerOrDialogNode.y()); + QThread::msleep(1000); + continue; + } + + // Проверяем на банеры перед пин-кодом + Node closeBannerNode = xmlScreenParser.tryToFindSecurityBanner(); + if (!closeBannerNode.isEmpty()) { + qDebug() << "--- tryToFindSecurityBanner"; + AdbUtils::makeTap(deviceId, closeBannerNode.x(), closeBannerNode.y()); + QThread::msleep(1000); + continue; + } + + // Проверяем, что это экран ввода пин-кода + if (!findAndInputPincode) { + if (const QList pincode = xmlScreenParser.tryToFindPinCode(pinCode, "Введите ПИН"); + pincode.size() == 4) { + qDebug() << "--- tryToFindPinCode"; + // делаем 4 тапа и ждем секунду и продолжаем + for (const Node &node: pincode) { + AdbUtils::makeTap(deviceId, node.x(), node.y()); + QThread::msleep(QRandomGenerator::global()->bounded(200, 901)); + } + // После ввода пин-код нужно сделать задержку, пока подгрузка идет, чтобы снова не спарсить + findAndInputPincode = true; + QThread::msleep(3000); + continue; + } + } + + QThread::msleep(1000); + + // Если что-то пойдет не по плану... + if (totalCounter > 100) { + return false; + } + } +} + + +void HomeScreenScript::start() { + // Достаем данные из БД по ИД транзакции, перед тем как запустить мы кладем в БД все данные + const QString deviceId = "6edc4a47"; + const QString packageName = "ru.rshb.dbo"; + const QString pinCode = "1102"; + const int width = 720; + const int height = 1600; + + const QString cardNumber = "7 1 8 3"; + // В том числе и данные на каком устройстве это делать + // TODO тут код работы с БД + + // +7 (964) 181-51-46 + const QString phoneNumber = "+79641815146"; + const QString bankName = "Альфа-Банк"; + const QString paySum = "10"; + + + xmlScreenParser.test(); + return; + + int payCounter = 0; + + if (false) { + // Оплата со страницы карты + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); + Node payBtn = xmlScreenParser.findButtonNode("Оплатить", false); + if (!payBtn.isEmpty()) { + AdbUtils::makeTap(deviceId, payBtn.x(), payBtn.y()); + QThread::msleep(1000); + } else { + qWarning() << "--- payBtn not found"; + } + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); + // TODO - По номеру карты В РСХБ и другие банки + Node payTitle = xmlScreenParser.findTextNode("Платежи и переводы"); + if (!payTitle.isEmpty() && payTitle.y() < 300) { + Node payByPhoneBtn = xmlScreenParser.findButtonNode("По номеру телефона В РСХБ и через", false); + if (!payByPhoneBtn.isEmpty()) { + AdbUtils::makeTap(deviceId, payByPhoneBtn.x(), payByPhoneBtn.y()); + QThread::msleep(1000); + } else { + qWarning() << "--- payByPhoneBtn not found"; + } + } else { + qWarning() << "--- payTitle not found"; + } + + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); + Node payByPhoneTitle = xmlScreenParser.findTextNode("Перевод по телефону"); + Node continueBtn = xmlScreenParser.findButtonNode("Продолжить", false); + if (!payByPhoneTitle.isEmpty() && payByPhoneTitle.y() < 300 && continueBtn.isEmpty()) { + Node inputPhone = xmlScreenParser.findFirstEditText(); + AdbUtils::makeTap(deviceId, inputPhone.x(), inputPhone.y()); + QThread::msleep(500); + AdbUtils::inputText(deviceId, phoneNumber); + QThread::msleep(1000); + } else { + qWarning() << "--- payByPhoneTitle not found (no continueBtn)"; + } + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); + QString payBankName; + payBankName = bankName == "Россельхозбанк" ? "Россельхозбанк" : "Другой банк через СБП"; + Node payOtherBankBtn = xmlScreenParser.findButtonNode(payBankName, false); + if (!payOtherBankBtn.isEmpty()) { + AdbUtils::makeTap(deviceId, payOtherBankBtn.x(), payOtherBankBtn.y()); + QThread::msleep(1000); + } else { + qWarning() << "--- payOtherBankBtn not found"; + } + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); + for (int i = 0; i < 5; ++i) { + Node allBanksTitle = xmlScreenParser.findTextNode("Банки участники"); + if (!allBanksTitle.isEmpty() && allBanksTitle.y() < 300) { + Node inputBankName = xmlScreenParser.findFirstEditText(); + AdbUtils::makeTap(deviceId, inputBankName.x(), inputBankName.y()); + QThread::msleep(500); + AdbUtils::enableAdbIMEKeyboard(deviceId); + AdbUtils::inputAdbIMEText(deviceId, bankName); + AdbUtils::disableAdbIMEKeyboard(deviceId); + QThread::msleep(1000); + break; + } else { + qWarning() << "--- allBanksTitle not found, i: " << i; + } + QThread::msleep(1000); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); + } + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); + for (int i = 0; i < 5; ++i) { + Node bankNameListItem = xmlScreenParser.findTextNode(bankName); + if (!bankNameListItem.isEmpty()) { + AdbUtils::makeTap(deviceId, bankNameListItem.x(), bankNameListItem.y()); + QThread::msleep(1000); + break; + } else { + qWarning() << "--- bankNameListItem not found, i: " << i; + } + QThread::msleep(1000); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); + } + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); + + Node payByPhoneScreenTitle = xmlScreenParser.findTextNode("Перевод по телефону"); + Node continuePayBtn = xmlScreenParser.findButtonNode("Продолжить", false); + if (!payByPhoneScreenTitle.isEmpty() && payByPhoneScreenTitle.y() < 300 && !continuePayBtn.isEmpty()) { + Node sumBtn = xmlScreenParser.findButtonNode("0", false); + if (!sumBtn.isEmpty()) { + AdbUtils::makeTap(deviceId, sumBtn.x(), sumBtn.y()); + QThread::msleep(500); + AdbUtils::inputText(deviceId, paySum); + QThread::msleep(500); + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); + Node readyBtn = xmlScreenParser.findTextNode("Готово"); + if (!readyBtn.isEmpty()) { + AdbUtils::makeTap(deviceId, readyBtn.x(), readyBtn.y()); + QThread::msleep(500); + AdbUtils::makeTap(deviceId, continuePayBtn.x(), continuePayBtn.y()); + QThread::msleep(1000); + } else { + qWarning() << "--- readyBtn not found"; + } + } else { + qWarning() << "--- sumBtn not found"; + } + } else { + qWarning() << "--- payByPhoneScreenTitle not found"; + } + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); + for (int i = 0; i < 5; ++i) { + // TODO кнопка содержит "Перевести 10 ₽" если будет от 1000 то скорее всвего будет "Перевести 1 000 ₽", + // TODO не ясно как форматирование будет, поэтому пока на начало зашьем + Node makePayBtn = xmlScreenParser.findButtonNode("Перевести ", true); + if (!makePayBtn.isEmpty()) { + AdbUtils::makeTap(deviceId, makePayBtn.x(), makePayBtn.y()); + QThread::msleep(500); + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); + if (const QList pincode = xmlScreenParser.tryToFindPinCode(pinCode, "Введите код доступа"); + pincode.size() == 4) { + // FIXME вынести в отдельный метод + // делаем 4 тапа и ждем секунду и продолжаем + for (const Node &node: pincode) { + AdbUtils::makeTap(deviceId, node.x(), node.y()); + QThread::msleep(QRandomGenerator::global()->bounded(200, 901)); + } + + // После ввода пин-кода, ждем 3с + QThread::msleep(3000); + } + break; + } else { + qWarning() << "--- makePayBtn not found, i: " << i; + } + QThread::msleep(1000); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); + } + } + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); + + for (int i = 0; i < 5; ++i) { + Node moreDetailBtn = xmlScreenParser.findButtonNode("Детали операции", false); + if (!moreDetailBtn.isEmpty()) { + AdbUtils::makeTap(deviceId, moreDetailBtn.x(), moreDetailBtn.y()); + QThread::msleep(200); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); + + Node paymentCompleted = xmlScreenParser.findTextNode("Исполнен", 0, 0, width, height / 2); + Node paymentInProgress = xmlScreenParser.findTextNode("Выполняется", 0, 0, width, height / 2); + + if (!paymentCompleted.isEmpty()) { + // Если вдруг исполнен + + } else if (!paymentInProgress.isEmpty()) { + // Если все еще в ожидании + + } else { + qWarning() << "--- paymentCompleted or paymentInProgress not found..."; + } + break; + } else { + qWarning() << "--- moreDetailBtn not found, i: " << i; + } + + QThread::msleep(1000); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); + } + + + return; + + int counter = 0; + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, counter)); + Node closeBannerOrDialogNode = xmlScreenParser.tryToFindBannerOrDialog(width, height); + if (closeBannerOrDialogNode.x() != -1 && closeBannerOrDialogNode.y() != -1) { + qDebug() << "--- tryToFindBannerOrDialog"; + AdbUtils::makeTap(deviceId, closeBannerOrDialogNode.x(), closeBannerOrDialogNode.y()); + QThread::msleep(1000); + } + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter)); + if (xmlScreenParser.isHomeScreen()) { + qDebug() << "--- isHomeScreen"; + if (Node allCardTitle = xmlScreenParser.findTextNode("Счета и карты"); !allCardTitle.isEmpty()) { + // 1. Если нет кнопки видимой, свайпаем. Кликаем на "Все продукты" + for (int i = 0; i < 5; ++i) { + Node allProductsBtn = xmlScreenParser.findButtonNode("Все продукты", false); + // Node allProductsBtn = xmlScreenParser.findTextNode("Курсы валют и металлов"); для теста + if (allProductsBtn.x() > 0 && allProductsBtn.y() > 0) { + qDebug() << "--- All Product in focus"; + AdbUtils::makeTap(deviceId, allProductsBtn.x(), allProductsBtn.y()); + QThread::msleep(1000); + break; + } + AdbUtils::makeSwipe(deviceId, + allCardTitle.x(), allCardTitle.y(), + allCardTitle.x(), allCardTitle.y() - 200); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter)); + } + + for (int i = 0; i < 5; ++i) { + Node allProductsTitle = xmlScreenParser.findTextNode("Все продукты"); + // Проверяем что вверху заголовок + if (!allProductsTitle.isEmpty() && allProductsTitle.y() < 300) { + qDebug() << "--- All Product screen"; + + // TODO тут делаем синхронизацию данных все карт + + Node cardNumberBtn = xmlScreenParser.findButtonNode(cardNumber, true); + AdbUtils::makeTap(deviceId, cardNumberBtn.x(), cardNumberBtn.y()); + break; + } + + QThread::msleep(1000); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter)); + } + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter)); + Node filterBtn = xmlScreenParser.findButtonNode("РСХБ Онлайн", false); + if (!filterBtn.isEmpty()) { + AdbUtils::makeTap(deviceId, filterBtn.x(), filterBtn.y()); + QThread::msleep(1000); + } else { + qWarning() << "--- filterBtn not found"; + } + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter)); + Node allTransactionFilerBtn = xmlScreenParser.findButtonNode("Все операции", false); + if (!allTransactionFilerBtn.isEmpty()) { + AdbUtils::makeTap(deviceId, allTransactionFilerBtn.x(), allTransactionFilerBtn.y()); + QThread::msleep(1000); + } else { + qWarning() << "--- allTransactionFilerBtn not found"; + } + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter)); + for (int i = 0; i < 5; ++i) { + QList transactions = xmlScreenParser.getLast2DaysTransactions(); + if (!transactions.empty()) { + for (const Node &transaction: transactions) { + qDebug() << transaction.text; + // TODO вот мы дошли до списка транзакций + } + break; + } + + QThread::msleep(1000); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter)); + } + + // + } else { + // пишем в БД, что не дошли до домашней страницы, и завершаемся + // сохраняем скрин приложения + // дделаем алерт + } + } else { + // пишем в БД, что не дошли до домашней страницы, и завершаемся + // сохраняем скрин приложения + // дделаем алерт + } + + return; + + // 1. Идем на домашнюю страницу + if (goToHomeScreen(deviceId, packageName, pinCode, width, height)) { + // мы на главном + // перед тем как юзать снова проверить на банеры + // Проверяем на банеры после пин-кодом + + + // Это уже для шага дальше + // если нулевые координаты, свайпаем вверх, чтобы текст "Счета и карты" был у верхнего края + } else { + // пишем в БД, что не дошли до домашней страницы, и завершаемся + // сохраняем скрин приложения + // дделаем алерт + } + + // 2. Идем дальше + + + emit finished(); +} + + +void HomeScreenScript::stop() { + m_running = false; +} diff --git a/android/rshb/HomeScreenScript.h b/android/rshb/HomeScreenScript.h new file mode 100644 index 0000000..b0c8a51 --- /dev/null +++ b/android/rshb/HomeScreenScript.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include "ScreenXmlParser.h" + +class HomeScreenScript final : public QObject { + Q_OBJECT + +public: + explicit HomeScreenScript(int transactionId, QObject *parent = nullptr); + + ~HomeScreenScript() override; + +public slots: + void start(); + + void stop(); + +signals: + void finished(); + +private: + bool m_running = true; + int transactionId; + ScreenXmlParser xmlScreenParser; + + bool goToHomeScreen( + const QString &deviceId, + const QString &packageName, + const QString &pinCode, + int width, + int height + ); +}; diff --git a/android/rshb/ScreenXmlParser.cpp b/android/rshb/ScreenXmlParser.cpp new file mode 100644 index 0000000..d4bed02 --- /dev/null +++ b/android/rshb/ScreenXmlParser.cpp @@ -0,0 +1,325 @@ +#include "ScreenXmlParser.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "android/xml/Node.h" + +ScreenXmlParser::ScreenXmlParser(QObject *parent) : QObject(parent) { +} + +ScreenXmlParser::~ScreenXmlParser() = default; + +static const QRegularExpression boundsRegex(R"(\[([0-9]+),([0-9]+)\]\[([0-9]+),([0-9]+)\])"); + +Node ScreenXmlParser::tryToFindBannerOrDialog(const int width, const int height) { + // 1. Ищем push banner - он перекрывает весь экран + Node first = m_nodes[0]; + for (const Node &node: m_nodes) { + // Если это однозначно, что пуш-баннер + if (node.resourceId == "aft-popup-pushNotification-iconClose" && node.className == "android.widget.Button") { + return node; + } + // Если есть кнопка "Позже" + if (node.text.trimmed() == "Позже" && node.className == "android.widget.Button") { + return node; + } + // Если нашли пустую кнпоку + if (node.className == "android.widget.Button" && node.clickable && node.text.isEmpty()) { + // + if (node.x() > width / 2 && node.y() < width / 2) { + return node; + } + } + } + return {}; +} + +/** + * Смотрим что есть текс "Мы заботимся о вашей безопасности" и + * кнопка "Отложить" + * @return координаты кнопки для закрытия + */ +Node ScreenXmlParser::tryToFindSecurityBanner() { + Node text; + Node closeBtn; + for (const Node &node: m_nodes) { + if (node.text.trimmed() == "Мы заботимся о вашей безопасности" && node.className == "android.app.Dialog") { + text = node; + } + if (node.text.trimmed() == "Отложить" && node.className == "android.widget.Button") { + closeBtn = node; + } + } + + // Не нужно проверять все, если хотя бы одна координата есть + if (!text.isEmpty() && !closeBtn.isEmpty()) { + return closeBtn; + } + return {}; +} + +Node ScreenXmlParser::findTextNode(const QString &text) { + for (const Node &node: m_nodes) { + if (node.className == "android.widget.TextView" && node.text.trimmed() == text) { + return node; + } + } + return {}; +} + +Node ScreenXmlParser::findTextNode(const QString &text, const int x1, const int y1, const int x2, const int y2) { + for (const Node &node: m_nodes) { + if (node.className == "android.widget.TextView" && node.text.trimmed() == text + && node.x() >= x1 && node.x() <= x2 && node.y() >= y1 && node.y() <= y2) { + return node; + } + } + return {}; +} + +Node ScreenXmlParser::findFirstEditText() { + for (const Node &node: m_nodes) { + if (node.className == "android.widget.EditText" && node.focusable) { + return node; + } + } + return {}; +} + + +Node ScreenXmlParser::findButtonNode(const QString &text, const bool contains) { + for (const Node &node: m_nodes) { + if (contains) { + if (node.className == "android.widget.Button" && node.text.contains(text)) { + return node; + } + } else { + if (node.className == "android.widget.Button" && node.text.trimmed() == text) { + return node; + } + } + } + return {}; +} + + +bool ScreenXmlParser::isHomeScreen() { + // Есть заголовок "Счета и карты" и кнопка "Все продукты" + Node text; + Node allProductBtn; + for (const Node &node: m_nodes) { + if (node.text.trimmed() == "Счета и карты" && node.className == "android.widget.TextView") { + text = node; + } + if (node.text.trimmed() == "Все продукты" && node.className == "android.widget.Button") { + allProductBtn = node; + } + } + return !text.isEmpty() && !allProductBtn.isEmpty(); +} + +QList ScreenXmlParser::findPinCodeNodes(const QString &pinCode) { + QMap buttons; + const QSet numberSet = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; + + for (const Node &node: m_nodes) { + const QString &text = node.text.trimmed(); + if (node.className == "android.widget.Button" && numberSet.contains(text)) { + buttons[text] = node; + } + } + + QList nodes; + for (int i = 0; i < pinCode.length(); ++i) { + nodes.append(buttons[QString(pinCode.at(i))]); + } + return nodes; +} + +QList ScreenXmlParser::tryToFindPinCode(const QString &pinCode, const QString &title) { + for (const Node &node: m_nodes) { + // если там есть такая кнопка значит банер есть, и пин-код не прожать + if (node.text.trimmed() == "Отложить" && node.className == "android.widget.Button") { + return {}; + } + } + for (const Node &node: m_nodes) { + // FIXME определение пин-кода лучше сделать + if (node.text.trimmed() == title) { + return findPinCodeNodes(pinCode); + } + } + return {}; +} + +QList ScreenXmlParser::getLast2DaysTransactions() { + QList nodes; + for (const Node &node: m_nodes) { + if (node.className == "android.widget.Button" && node.text.contains("рубл") + && (node.text.contains("Минус") || node.text.contains("Плюс"))) { + nodes.append(node); + } + } + return nodes; +} + +/** +* + */ +void ScreenXmlParser::parseAndSaveXml(const QString &xml) { + m_xml = xml; + m_nodes.clear(); + + QXmlStreamReader reader(xml); + while (!reader.atEnd()) { + reader.readNext(); + if (reader.isStartElement() && reader.name() == "node") { + Node node = Node(); + if (reader.attributes().hasAttribute("bounds")) { + QString bounds = reader.attributes().value("bounds").toString(); + if (QRegularExpressionMatch match = boundsRegex.match(bounds); match.hasMatch()) { + node.setBounds( + match.captured(1).toInt(), + match.captured(2).toInt(), + match.captured(3).toInt(), + match.captured(4).toInt() + ); + } + } + if (reader.attributes().hasAttribute("text")) { + node.text = reader.attributes().value("text").toString(); + } + + if (reader.attributes().hasAttribute("class")) { + node.className = reader.attributes().value("class").toString(); + } + + if (reader.attributes().hasAttribute("clickable")) { + node.clickable = reader.attributes().value("clickable").toString() == "true"; + } + if (reader.attributes().hasAttribute("focusable")) { + node.focusable = reader.attributes().value("focusable").toString() == "true"; + } + if (reader.attributes().hasAttribute("resource-id")) { + node.resourceId = reader.attributes().value("resource-id").toString(); + } + + m_nodes.append(node); + } + } +} + +void traverseNodes(const QDomNode &node) { + if (node.isElement()) { + QDomElement elem = node.toElement(); + + if (node.isElement()) { + QDomElement elem = node.toElement(); + + if (elem.tagName() == "node" && + elem.attribute("class") == "android.widget.Button" && + elem.attribute("text") == "Детали операции") { + // Ищем сиблингов от текущего узла и далее + QDomNodeList siblings = elem.parentNode().childNodes(); + + // Получение строки для анализа + // QString xmlString; + // QTextStream stream(&xmlString); + // elem.parentNode().save(stream, 0); + // qDebug() << xmlString; + // return; + + for (int j = 0; j < siblings.count(); ++j) { + QDomNode sib = siblings.at(j); + if (!sib.isElement()) continue; + QDomElement sibElem = sib.toElement(); + + + qDebug() << "Node:" + << sibElem.attribute("index") + << sibElem.attribute("text"); + // << sibElem.attribute("class"); + + if (sibElem.hasChildNodes()) { + QDomNodeList sibChildren = sibElem.childNodes(); + for (int k = 0; k < sibChildren.count(); ++k) { + QDomNode sibChild = sibChildren.at(k); + if (!sibChild.isElement()) continue; + QDomElement sibChildElem = sibChild.toElement(); + qDebug() << "... Node:" + << sibChildElem.attribute("index") + << sibChildElem.attribute("text"); + // << sibChildElem.attribute("class"); + + + if (sibChildElem.hasChildNodes()) { + QDomNodeList sibChildren2 = sibChildElem.childNodes(); + for (int k = 0; k < sibChildren2.count(); ++k) { + QDomNode sibChild2 = sibChildren2.at(k); + if (!sibChild2.isElement()) continue; + QDomElement sibChildElem2 = sibChild2.toElement(); + + qDebug() << "...... Node:" + << sibChildElem2.attribute("index") + << sibChildElem2.attribute("text"); + // << sibChildElem2.attribute("class"); + + if (sibChildElem2.hasChildNodes()) { + QDomNodeList sibChildren3 = sibChildElem2.childNodes(); + for (int k = 0; k < sibChildren3.count(); ++k) { + QDomNode sibChild3 = sibChildren3.at(k); + if (!sibChild3.isElement()) continue; + QDomElement sibChildElem3 = sibChild3.toElement(); + + qDebug() << "......... Node:" + << sibChildElem3.attribute("index") + << sibChildElem3.attribute("text"); + // << sibChildElem2.attribute("class"); + } + } + } + } + } + } + } + } + } + } + + // Рекурсивно обходим всех детей + QDomNode child = node.firstChild(); + while (!child.isNull()) { + traverseNodes(child); + child = child.nextSibling(); + } +} + +void ScreenXmlParser::test() { + QFile file("assets/rshb/pay/pay_by_phone_history_expanded.xml"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qDebug() << "Cannot open file"; + } + + QDomDocument doc; + if (!doc.setContent(&file)) { + qDebug() << "Failed to parse XML"; + } + file.close(); + + qDebug() << "Start parsing XML:"; + + QDomElement root = doc.documentElement(); + traverseNodes(root); +} diff --git a/android/rshb/ScreenXmlParser.h b/android/rshb/ScreenXmlParser.h new file mode 100644 index 0000000..4cda0f0 --- /dev/null +++ b/android/rshb/ScreenXmlParser.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include "android/xml/Node.h" + +class ScreenXmlParser final : public QObject { + Q_OBJECT + +public: + explicit ScreenXmlParser(QObject *parent = nullptr); + + ~ScreenXmlParser() override; + + Node tryToFindBannerOrDialog(int width, int height); + + Node tryToFindSecurityBanner(); + + Node findTextNode(const QString &text); + Node findTextNode(const QString &text, int x1, int y1, int x2, int y2); + + Node findFirstEditText(); + + Node findButtonNode(const QString &text, bool contains); + + bool isHomeScreen(); + + + QList tryToFindPinCode(const QString &pinCode, const QString &title); + + QList getLast2DaysTransactions(); + + void parseAndSaveXml(const QString &xml); + + void test(); + +private: + QList m_nodes; + QString m_xml; + + QList findPinCodeNodes(const QString &pinCode); +}; diff --git a/assets/ADBKeyboard.apk b/assets/ADBKeyboard.apk new file mode 100644 index 0000000..6e54ba9 Binary files /dev/null and b/assets/ADBKeyboard.apk differ diff --git a/assets/alpha_contact_permission.xml b/assets/alpha_contact_permission.xml new file mode 100644 index 0000000..0855e70 --- /dev/null +++ b/assets/alpha_contact_permission.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/capture.sh b/assets/capture.sh new file mode 100755 index 0000000..2230c0c --- /dev/null +++ b/assets/capture.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# Проверка аргумента +if [ -z "$1" ]; then + echo "Usage: $0 " + exit 1 +fi + +NAME="$1" +DEVICE_ID="6edc4a47" + +# Снимок UI Automator +adb -s "$DEVICE_ID" shell 'uiautomator dump /sdcard/uidump.xml' && \ +adb -s "$DEVICE_ID" pull /sdcard/uidump.xml "${NAME}.xml" && \ +adb -s "$DEVICE_ID" shell 'rm /sdcard/uidump.xml' + +# Скриншот экрана +adb -s "$DEVICE_ID" shell 'screencap -p /sdcard/screen.png' && \ +adb -s "$DEVICE_ID" pull /sdcard/screen.png "${NAME}.png" && \ +adb -s "$DEVICE_ID" shell 'rm /sdcard/screen.png' + diff --git a/assets/rshb/all_products.png b/assets/rshb/all_products.png new file mode 100644 index 0000000..3520293 Binary files /dev/null and b/assets/rshb/all_products.png differ diff --git a/assets/rshb/all_products.xml b/assets/rshb/all_products.xml new file mode 100644 index 0000000..c43f03f --- /dev/null +++ b/assets/rshb/all_products.xml @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/banks.txt b/assets/rshb/banks.txt new file mode 100644 index 0000000..9f8fd6b --- /dev/null +++ b/assets/rshb/banks.txt @@ -0,0 +1,205 @@ +Сбербанк +Т-Банк +ВТБ +Альфа-Банк +Филиал Открытие БМ-Банк +Почта Банк +Райффайзен Банк +Газпромбанк +Совкомбанк +ПСБ +Ак Барс Банк +МКБ (Московский кредитный банк) +Банк Синара +МТС-Банк +Золотая Корона – Korona (РНКО Платежный Центр) +Банк Санкт-Петербург +Банк Русский Стандарт +VK Pay - ВК Платёжные решения +Wallet One (РНКО Единая касса) +Wildberries (Вайлдберриз Банк) +АБ РОССИЯ +АИКБ Енисейский объединенный банк +АКБ Держава +АКБ ЕВРОФИНАНС МОСНАРБАНК +АКБ Ланта Банк +АКБ НРБанк +АКБ СЛАВИЯ +АКБ Тендер Банк +АКИБАНК +Абсолют Банк +Авито Кошелек (Платежный конструктор) +Авто Финанс Банк +Автоторгбанк +Азиатско-Тихоокеанский Банк +Алеф-Банк +Алмазэргиэнбанк +БАНК МОСКВА СИТИ +БАНК ОРЕНБУРГ +БАНК СГБ +БАНК СНГБ +БАНК УРАЛСИБ +ББР Банк +БКС Банк +Банк АВАНГАРД +Банк АЛЕКСАНДРОВСКИЙ +Банк Аверс +Банк Агророс +Банк Акцепт +Банк БЖФ +Банк ВБРР +Банк Венец +Банк Вологжанин +Банк ДОМ.РФ +Банк ЗЕНИТ +Банк Заречье +Банк ИПБ +Банк ИТУРУП +Банк Интеза +Банк Йошкар-Ола +Банк Кремлевский +Банк Кузнецкий +Банк Левобережный +Банк МБА МОСКВА +Банк НОВИКОМ (НОВИКОМБАНК) +Банк Национальный стандарт +Банк Объединенный капитал +Банк Оранжевый +Банк ПСКБ +Банк Приморье +Банк РЕСО Кредит +Банк РСИ +Банк Развитие-Столица +Банк Саратов +Банк Снежинский +Банк Уралфинанс +Банк ФИНАМ +Банк Финсервис +Банк ЦентроКредит +Банк ЧБРР +Бланк банк +Братский АНКБ +БыстроБанк +ВЛАДБИЗНЕСБАНК +ВНЕШФИНБАНК +ВУЗ-банк +ГЕНБАНК +ГОРБАНК +ГУТА-БАНК +Газтрансбанк +Газэнергобанк +Далена Банк +Дальневосточный банк +Датабанк +Драйв Клик Банк +ЖИВАГО БАНК +Земский банк +ИК Банк +ИС Банк +ИШБАНК +Инбанк +Ингосстрах Банк +КБ РостФинанс +КБ АГРОПРОМКРЕДИТ +КБ АРЕСБАНК +КБ Долинск +КБ Крокус Банк +КБ ЛОКО-Банк +КБ Модульбанк +КБ Москоммерцбанк +КБ Пойдём +КБ Солидарность +КБ Стройлесбанк +КБ Хлынов +КБ ЭНЕРГОТРАНСБАНК +КБЭР Банк Казани +КОШЕЛЕВ-БАНК +Контур.Банк +Кошелек ЦУПИС (Мобильная карта) +Кредит Европа Банк (Россия) +Кредит Урал Банк +Кубань Кредит +Кубаньторгбанк +Кузнецкбизнесбанк +МБ Банк +МЕЖДУНАРОДНЫЙ ФИНАНСОВЫЙ КЛУБ +МЕТКОМБАНК +МОНЕТА +МОРСКОЙ БАНК +МОСКОМБАНК +МС Банк Рус +МТС Деньги (ЭКСИ Банк) +Металлинвестбанк +Мир Привилегий (МП Банк) +НБД-Банк +НИКО-БАНК +НК Банк +НКО МОБИ.Деньги +НКО Расчетные Решения +НОКССБАНК +НС Банк +Нацинвестпромбанк +Новобанк +Новый век +Норвик Банк +ОТП Банк +Озон Банк (Ozon) +ПЕРВОУРАЛЬСКБАНК +Первый Дортрансбанк +Первый Инвестиционный Банк +Прио-Внешторгбанк +ПроБанк +РНКБ Банк +РУСНАРБАНК +Реалист Банк +Ренессанс Банк (Ренессанс Кредит) +Рокетбанк +РосДорБанк +Роял Кредит Банк +Русьуниверсалбанк +СДМ-Банк +СИБСОЦБАНК +СИНКО-БАНК +СКБ Примсоцбанк +СОЦИУМ БАНК +Свой Банк +Северный Народный Банк +Солид Банк +Ставропольпромстройбанк +ТАТСОЦБАНК +ТРАНСКАПИТАЛБАНК +Таврический Банк +Тимер Банк +Тойота Банк +Тольяттихимбанк +Томскпромстройбанк +Точка Банк +Трансстройбанк +УБРиР +УКБ Белгородсоцбанк +УРАЛПРОМБАНК +Углеметбанк +Урал ФД +ФИНСТАР БАНК +ФОРА-БАНК +Форштадт +Хайс +Хакасский муниципальный банк +ЦМРБанк +Центр-инвест +Цифра банк +ЧЕЛИНДБАНК +ЧЕЛЯБИНВЕСТБАНК +ЭЛПЛАТ +Экономбанк +Экспобанк +Энергобанк +Эс-Би-Ай Банк +ЮГ-Инвестбанк +ЮМани +ЮНИСТРИМ БАНК +ЮниКредит Банк +ЯРИНТЕРБАНК +Яндекс +банк Раунд +банк Элита \ No newline at end of file diff --git a/assets/rshb/card_screen.png b/assets/rshb/card_screen.png new file mode 100644 index 0000000..76e973a Binary files /dev/null and b/assets/rshb/card_screen.png differ diff --git a/assets/rshb/card_screen.xml b/assets/rshb/card_screen.xml new file mode 100644 index 0000000..9fffac4 --- /dev/null +++ b/assets/rshb/card_screen.xml @@ -0,0 +1,1034 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/card_screen_all_transactions.png b/assets/rshb/card_screen_all_transactions.png new file mode 100644 index 0000000..0984d96 Binary files /dev/null and b/assets/rshb/card_screen_all_transactions.png differ diff --git a/assets/rshb/card_screen_all_transactions.xml b/assets/rshb/card_screen_all_transactions.xml new file mode 100644 index 0000000..8e55d70 --- /dev/null +++ b/assets/rshb/card_screen_all_transactions.xml @@ -0,0 +1,898 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/card_screen_open_filer.png b/assets/rshb/card_screen_open_filer.png new file mode 100644 index 0000000..9ac5b1a Binary files /dev/null and b/assets/rshb/card_screen_open_filer.png differ diff --git a/assets/rshb/card_screen_open_filer.xml b/assets/rshb/card_screen_open_filer.xml new file mode 100644 index 0000000..19b64c3 --- /dev/null +++ b/assets/rshb/card_screen_open_filer.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/error/not_found_number+bank.png b/assets/rshb/error/not_found_number+bank.png new file mode 100644 index 0000000..84dbf0a Binary files /dev/null and b/assets/rshb/error/not_found_number+bank.png differ diff --git a/assets/rshb/error/not_found_number+bank.xml b/assets/rshb/error/not_found_number+bank.xml new file mode 100644 index 0000000..1ec7ef7 --- /dev/null +++ b/assets/rshb/error/not_found_number+bank.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/error/pay_by_phone_unknown_error.png b/assets/rshb/error/pay_by_phone_unknown_error.png new file mode 100644 index 0000000..8a13eea Binary files /dev/null and b/assets/rshb/error/pay_by_phone_unknown_error.png differ diff --git a/assets/rshb/error/pay_by_phone_unknown_error.xml b/assets/rshb/error/pay_by_phone_unknown_error.xml new file mode 100644 index 0000000..af79288 --- /dev/null +++ b/assets/rshb/error/pay_by_phone_unknown_error.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/rshb/error/session_expired.png b/assets/rshb/error/session_expired.png new file mode 100644 index 0000000..c52c6ed Binary files /dev/null and b/assets/rshb/error/session_expired.png differ diff --git a/assets/rshb/error/session_expired.xml b/assets/rshb/error/session_expired.xml new file mode 100644 index 0000000..490111d --- /dev/null +++ b/assets/rshb/error/session_expired.xml @@ -0,0 +1,307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/home_page_push_banner.png b/assets/rshb/home_page_push_banner.png new file mode 100644 index 0000000..40f1000 Binary files /dev/null and b/assets/rshb/home_page_push_banner.png differ diff --git a/assets/rshb/home_page_push_banner.xml b/assets/rshb/home_page_push_banner.xml new file mode 100644 index 0000000..6d58a8d --- /dev/null +++ b/assets/rshb/home_page_push_banner.xml @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/home_screen.png b/assets/rshb/home_screen.png new file mode 100644 index 0000000..e35c012 Binary files /dev/null and b/assets/rshb/home_screen.png differ diff --git a/assets/rshb/home_screen.xml b/assets/rshb/home_screen.xml new file mode 100644 index 0000000..ec705e9 --- /dev/null +++ b/assets/rshb/home_screen.xml @@ -0,0 +1,1721 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/pay/bank_list.png b/assets/rshb/pay/bank_list.png new file mode 100644 index 0000000..ab3b6d8 Binary files /dev/null and b/assets/rshb/pay/bank_list.png differ diff --git a/assets/rshb/pay/bank_list.xml b/assets/rshb/pay/bank_list.xml new file mode 100644 index 0000000..4c07cce --- /dev/null +++ b/assets/rshb/pay/bank_list.xml @@ -0,0 +1,1326 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/pay/pay_by_phone.png b/assets/rshb/pay/pay_by_phone.png new file mode 100644 index 0000000..70d1b22 Binary files /dev/null and b/assets/rshb/pay/pay_by_phone.png differ diff --git a/assets/rshb/pay/pay_by_phone.xml b/assets/rshb/pay/pay_by_phone.xml new file mode 100644 index 0000000..9611f52 --- /dev/null +++ b/assets/rshb/pay/pay_by_phone.xml @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/pay/pay_by_phone_after_input.png b/assets/rshb/pay/pay_by_phone_after_input.png new file mode 100644 index 0000000..c2b0a88 Binary files /dev/null and b/assets/rshb/pay/pay_by_phone_after_input.png differ diff --git a/assets/rshb/pay/pay_by_phone_after_input.xml b/assets/rshb/pay/pay_by_phone_after_input.xml new file mode 100644 index 0000000..c3008ac --- /dev/null +++ b/assets/rshb/pay/pay_by_phone_after_input.xml @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/pay/pay_by_phone_complete.png b/assets/rshb/pay/pay_by_phone_complete.png new file mode 100644 index 0000000..d6fe31a Binary files /dev/null and b/assets/rshb/pay/pay_by_phone_complete.png differ diff --git a/assets/rshb/pay/pay_by_phone_complete.xml b/assets/rshb/pay/pay_by_phone_complete.xml new file mode 100644 index 0000000..ac38770 --- /dev/null +++ b/assets/rshb/pay/pay_by_phone_complete.xml @@ -0,0 +1,292 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/pay/pay_by_phone_complete_expanded.png b/assets/rshb/pay/pay_by_phone_complete_expanded.png new file mode 100644 index 0000000..bc445a7 Binary files /dev/null and b/assets/rshb/pay/pay_by_phone_complete_expanded.png differ diff --git a/assets/rshb/pay/pay_by_phone_complete_expanded.xml b/assets/rshb/pay/pay_by_phone_complete_expanded.xml new file mode 100644 index 0000000..bab4eae --- /dev/null +++ b/assets/rshb/pay/pay_by_phone_complete_expanded.xml @@ -0,0 +1,534 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/pay/pay_by_phone_complete_wait.png b/assets/rshb/pay/pay_by_phone_complete_wait.png new file mode 100644 index 0000000..771b867 Binary files /dev/null and b/assets/rshb/pay/pay_by_phone_complete_wait.png differ diff --git a/assets/rshb/pay/pay_by_phone_complete_wait.xml b/assets/rshb/pay/pay_by_phone_complete_wait.xml new file mode 100644 index 0000000..5d20cbc --- /dev/null +++ b/assets/rshb/pay/pay_by_phone_complete_wait.xml @@ -0,0 +1,292 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/pay/pay_by_phone_complete_wait_expanded.png b/assets/rshb/pay/pay_by_phone_complete_wait_expanded.png new file mode 100644 index 0000000..727accf Binary files /dev/null and b/assets/rshb/pay/pay_by_phone_complete_wait_expanded.png differ diff --git a/assets/rshb/pay/pay_by_phone_complete_wait_expanded.xml b/assets/rshb/pay/pay_by_phone_complete_wait_expanded.xml new file mode 100644 index 0000000..942b0a3 --- /dev/null +++ b/assets/rshb/pay/pay_by_phone_complete_wait_expanded.xml @@ -0,0 +1,534 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/pay/pay_by_phone_history_expanded.png b/assets/rshb/pay/pay_by_phone_history_expanded.png new file mode 100644 index 0000000..b34f930 Binary files /dev/null and b/assets/rshb/pay/pay_by_phone_history_expanded.png differ diff --git a/assets/rshb/pay/pay_by_phone_history_expanded.xml b/assets/rshb/pay/pay_by_phone_history_expanded.xml new file mode 100644 index 0000000..96ddc16 --- /dev/null +++ b/assets/rshb/pay/pay_by_phone_history_expanded.xml @@ -0,0 +1,409 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/pay/pay_by_phone_input_pincode.png b/assets/rshb/pay/pay_by_phone_input_pincode.png new file mode 100644 index 0000000..01c29e0 Binary files /dev/null and b/assets/rshb/pay/pay_by_phone_input_pincode.png differ diff --git a/assets/rshb/pay/pay_by_phone_input_pincode.xml b/assets/rshb/pay/pay_by_phone_input_pincode.xml new file mode 100644 index 0000000..95580cb --- /dev/null +++ b/assets/rshb/pay/pay_by_phone_input_pincode.xml @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/pay/pay_by_phone_input_sum.png b/assets/rshb/pay/pay_by_phone_input_sum.png new file mode 100644 index 0000000..4afdace Binary files /dev/null and b/assets/rshb/pay/pay_by_phone_input_sum.png differ diff --git a/assets/rshb/pay/pay_by_phone_input_sum.xml b/assets/rshb/pay/pay_by_phone_input_sum.xml new file mode 100644 index 0000000..eb99841 --- /dev/null +++ b/assets/rshb/pay/pay_by_phone_input_sum.xml @@ -0,0 +1,435 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/pay/pay_by_phone_ready_to_transfer.png b/assets/rshb/pay/pay_by_phone_ready_to_transfer.png new file mode 100644 index 0000000..c08dd44 Binary files /dev/null and b/assets/rshb/pay/pay_by_phone_ready_to_transfer.png differ diff --git a/assets/rshb/pay/pay_by_phone_ready_to_transfer.xml b/assets/rshb/pay/pay_by_phone_ready_to_transfer.xml new file mode 100644 index 0000000..2462221 --- /dev/null +++ b/assets/rshb/pay/pay_by_phone_ready_to_transfer.xml @@ -0,0 +1,437 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/pay/pay_by_phone_screen.png b/assets/rshb/pay/pay_by_phone_screen.png new file mode 100644 index 0000000..166bc31 Binary files /dev/null and b/assets/rshb/pay/pay_by_phone_screen.png differ diff --git a/assets/rshb/pay/pay_by_phone_screen.xml b/assets/rshb/pay/pay_by_phone_screen.xml new file mode 100644 index 0000000..c3ced14 --- /dev/null +++ b/assets/rshb/pay/pay_by_phone_screen.xml @@ -0,0 +1,430 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/pay/pay_variants.png b/assets/rshb/pay/pay_variants.png new file mode 100644 index 0000000..72664c8 Binary files /dev/null and b/assets/rshb/pay/pay_variants.png differ diff --git a/assets/rshb/pay/pay_variants.xml b/assets/rshb/pay/pay_variants.xml new file mode 100644 index 0000000..503b351 --- /dev/null +++ b/assets/rshb/pay/pay_variants.xml @@ -0,0 +1,635 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/pin_code.png b/assets/rshb/pin_code.png new file mode 100644 index 0000000..4e88831 Binary files /dev/null and b/assets/rshb/pin_code.png differ diff --git a/assets/rshb/pin_code.xml b/assets/rshb/pin_code.xml new file mode 100644 index 0000000..1233ccf --- /dev/null +++ b/assets/rshb/pin_code.xml @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/start_security_banner.png b/assets/rshb/start_security_banner.png new file mode 100644 index 0000000..9fce31b Binary files /dev/null and b/assets/rshb/start_security_banner.png differ diff --git a/assets/rshb/start_security_banner.xml b/assets/rshb/start_security_banner.xml new file mode 100644 index 0000000..45e6b56 --- /dev/null +++ b/assets/rshb/start_security_banner.xml @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/rshb/story_banner.png b/assets/rshb/story_banner.png new file mode 100644 index 0000000..55588f3 Binary files /dev/null and b/assets/rshb/story_banner.png differ diff --git a/assets/rshb/story_banner.xml b/assets/rshb/story_banner.xml new file mode 100644 index 0000000..ff41760 --- /dev/null +++ b/assets/rshb/story_banner.xml @@ -0,0 +1,1312 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..e2efd37 --- /dev/null +++ b/config.ini @@ -0,0 +1,6 @@ +[apps] +list=rshb + +[rshb] +name=Рос Сельхоз Банк +android_package_name=ru.rshb.dbo \ No newline at end of file diff --git a/main.cpp b/main.cpp index c22b4ca..ddfe0aa 100644 --- a/main.cpp +++ b/main.cpp @@ -15,6 +15,8 @@ #include #include +#include "rshb/HomeScreenScript.h" + void setupWorkerAndThread(QCoreApplication &app) { auto *thread = new QThread; auto *deviceScreenerWorker = new DeviceScreener; @@ -47,17 +49,17 @@ void setupWorkerAndThread(QCoreApplication &app) { void setupScriptAutoPaymentAndThread(QCoreApplication &app) { auto *thread = new QThread; - auto *scriptWorker = new Script; + auto *scriptWorker = new HomeScreenScript(0); // Перемещаем worker в новый поток thread. // Это значит, что все слоты worker, вызываемые через connect(), теперь будут исполняться в этом фоновом потоке. scriptWorker->moveToThread(thread); // Когда поток стартует, запускаем start() - QObject::connect(thread, &QThread::started, scriptWorker, &Script::start); + QObject::connect(thread, &QThread::started, scriptWorker, &HomeScreenScript::start); // Когда работа (worker) завершена (emit finished()), останавливаем поток - QObject::connect(scriptWorker, &Script::finished, thread, &QThread::quit); + QObject::connect(scriptWorker, &HomeScreenScript::finished, thread, &QThread::quit); // Удаляем работу (worker) (deleteLater) - QObject::connect(scriptWorker, &Script::finished, scriptWorker, &QObject::deleteLater); + QObject::connect(scriptWorker, &HomeScreenScript::finished, scriptWorker, &QObject::deleteLater); // Удаляем поток (deleteLater) QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater); diff --git a/migrations.sql b/migrations.sql index 03467ed..ddbf7bd 100644 --- a/migrations.sql +++ b/migrations.sql @@ -12,6 +12,18 @@ CREATE TABLE IF NOT EXISTS devices image BLOB ); +CREATE TABLE IF NOT EXISTS applications +( + id TEXT PRIMARY KEY, + device_id TEXT, + pin_code TEXT, + name TEXT, + status TEXT, + comment TEXT, + image BLOB, + FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE +); + CREATE TABLE IF NOT EXISTS accounts ( id INTEGER PRIMARY KEY AUTOINCREMENT, diff --git a/models/android/xml/Node.h b/models/android/xml/Node.h new file mode 100644 index 0000000..cbc7b64 --- /dev/null +++ b/models/android/xml/Node.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +class Node { +public: + Node() = default; + + void setBounds(const int x1, const int y1, const int x2, const int y2) { + m_x1 = x1; + m_y1 = y1; + m_x2 = x2; + m_y2 = y2; + } + + bool isEmpty() { + return m_x1 < 0 && m_y1 < 0 && m_x2 < 0 && m_y2 < 0 && text.isEmpty() && className.isEmpty(); + } + + /** + * const int x = (x1() + x2()) / 2; + * const int y = (y1() + y2()) / 2; + */ + [[nodiscard]] int x() const { + if (m_x1 < 0) { + return -1; + } + return m_x1 + (m_x2 - m_x1) / 2; + } + + [[nodiscard]] int y() const { + if (m_y1 < 0) { + return -1; + } + return m_y1 + (m_y2 - m_y1) / 2; + } + + QString text; + QString resourceId; + QString className; + bool clickable = false; + bool focusable = false; + +private: + int m_x1 = -1, m_y1 = -1, m_x2 = -1, m_y2 = -1; +};