#include #include #include #include #include "android_controller.h" #include "android_utils.h" #include "ui/controllers/importController.h" namespace { AndroidController *s_instance = nullptr; constexpr auto QT_ANDROID_CONTROLLER_CLASS = "org/amnezia/vpn/qt/QtAndroidController"; } // namespace AndroidController::AndroidController() : QObject() { connect(this, &AndroidController::status, this, [this](AndroidController::ConnectionState state) { qDebug() << "Android event: status; state:" << textConnectionState(state); if (isWaitingStatus) { qDebug() << "Initialization by service status"; isWaitingStatus = false; emit initConnectionState(convertState(state)); } }, Qt::QueuedConnection); connect( this, &AndroidController::serviceDisconnected, this, [this]() { qDebug() << "Android event: service disconnected"; isWaitingStatus = true; emit connectionStateChanged(Vpn::ConnectionState::Disconnected); }, Qt::QueuedConnection); connect( this, &AndroidController::serviceError, this, [this]() { qDebug() << "Android event: service error"; // todo: add error message emit connectionStateChanged(Vpn::ConnectionState::Error); }, Qt::QueuedConnection); connect( this, &AndroidController::vpnPermissionRejected, this, [this]() { qWarning() << "Android event: VPN permission rejected"; emit connectionStateChanged(Vpn::ConnectionState::Disconnected); }, Qt::QueuedConnection); connect( this, &AndroidController::vpnConnected, this, [this]() { qDebug() << "Android event: VPN connected"; emit connectionStateChanged(Vpn::ConnectionState::Connected); }, Qt::QueuedConnection); connect( this, &AndroidController::vpnDisconnected, this, [this]() { qDebug() << "Android event: VPN disconnected"; emit connectionStateChanged(Vpn::ConnectionState::Disconnected); }, Qt::QueuedConnection); connect( this, &AndroidController::vpnReconnecting, this, [this]() { qDebug() << "Android event: VPN reconnecting"; emit connectionStateChanged(Vpn::ConnectionState::Reconnecting); }, Qt::QueuedConnection); connect( this, &AndroidController::configImported, this, [this](const QString& config) { qDebug() << "Android event: config import"; emit importConfigFromOutside(config); }, Qt::QueuedConnection); } AndroidController *AndroidController::instance() { if (!s_instance) { s_instance = new AndroidController(); } return s_instance; } bool AndroidController::initialize() { qDebug() << "Initialize AndroidController"; const JNINativeMethod methods[] = { {"onStatus", "(I)V", reinterpret_cast(onStatus)}, {"onServiceDisconnected", "()V", reinterpret_cast(onServiceDisconnected)}, {"onServiceError", "()V", reinterpret_cast(onServiceError)}, {"onVpnPermissionRejected", "()V", reinterpret_cast(onVpnPermissionRejected)}, {"onVpnConnected", "()V", reinterpret_cast(onVpnConnected)}, {"onVpnDisconnected", "()V", reinterpret_cast(onVpnDisconnected)}, {"onVpnReconnecting", "()V", reinterpret_cast(onVpnReconnecting)}, {"onStatisticsUpdate", "(JJ)V", reinterpret_cast(onStatisticsUpdate)}, {"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast(onFileOpened)}, {"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast(onConfigImported)}, {"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast(decodeQrCode)} }; QJniEnvironment env; bool registered = env.registerNativeMethods(QT_ANDROID_CONTROLLER_CLASS, methods, sizeof(methods) / sizeof(JNINativeMethod)); if (!registered) { qCritical() << "Failed native method registration"; return false; } qtAndroidControllerInitialized(); return true; } // static template auto AndroidController::callActivityMethod(const char *methodName, const char *signature, const std::function &defValue, Args &&...args) { qDebug() << "Call activity method:" << methodName; QJniObject activity = AndroidUtils::getActivity(); if (activity.isValid()) { return activity.callMethod(methodName, signature, std::forward(args)...); } else { qCritical() << "Activity is not valid"; return defValue(); } } // static template void AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args) { callActivityMethod(methodName, signature, [] {}, std::forward(args)...); } ErrorCode AndroidController::start(const QJsonObject &vpnConfig) { isWaitingStatus = false; auto config = QJsonDocument(vpnConfig).toJson(); callActivityMethod("start", "(Ljava/lang/String;)V", QJniObject::fromString(config).object()); return NoError; } void AndroidController::stop() { callActivityMethod("stop", "()V"); } void AndroidController::saveFile(const QString &fileName, const QString &data) { callActivityMethod("saveFile", "(Ljava/lang/String;Ljava/lang/String;)V", QJniObject::fromString(fileName).object(), QJniObject::fromString(data).object()); } QString AndroidController::openFile(const QString &filter) { QEventLoop wait; QString fileName; connect(this, &AndroidController::fileOpened, this, [&fileName, &wait](const QString &uri) { qDebug() << "Android event: file opened; uri:" << uri; fileName = QQmlFile::urlToLocalFileOrQrc(uri); qDebug() << "Android opened filename:" << fileName; wait.quit(); }, static_cast(Qt::QueuedConnection | Qt::SingleShotConnection)); callActivityMethod("openFile", "(Ljava/lang/String;)V", QJniObject::fromString(filter).object()); wait.exec(); return fileName; } void AndroidController::setNotificationText(const QString &title, const QString &message, int timerSec) { callActivityMethod("setNotificationText", "(Ljava/lang/String;Ljava/lang/String;I)V", QJniObject::fromString(title).object(), QJniObject::fromString(message).object(), (jint) timerSec); } void AndroidController::startQrReaderActivity() { callActivityMethod("startQrCodeReader", "()V"); } void AndroidController::qtAndroidControllerInitialized() { callActivityMethod("qtAndroidControllerInitialized", "()V"); } // static Vpn::ConnectionState AndroidController::convertState(AndroidController::ConnectionState state) { switch (state) { case AndroidController::ConnectionState::CONNECTED: return Vpn::ConnectionState::Connected; case AndroidController::ConnectionState::CONNECTING: return Vpn::ConnectionState::Connecting; case AndroidController::ConnectionState::DISCONNECTED: return Vpn::ConnectionState::Disconnected; case AndroidController::ConnectionState::DISCONNECTING: return Vpn::ConnectionState::Disconnecting; case AndroidController::ConnectionState::RECONNECTING: return Vpn::ConnectionState::Reconnecting; case AndroidController::ConnectionState::UNKNOWN: return Vpn::ConnectionState::Unknown; } } // static QString AndroidController::textConnectionState(AndroidController::ConnectionState state) { switch (state) { case AndroidController::ConnectionState::CONNECTED: return "CONNECTED"; case AndroidController::ConnectionState::CONNECTING: return "CONNECTING"; case AndroidController::ConnectionState::DISCONNECTED: return "DISCONNECTED"; case AndroidController::ConnectionState::DISCONNECTING: return "DISCONNECTING"; case AndroidController::ConnectionState::RECONNECTING: return "RECONNECTING"; case AndroidController::ConnectionState::UNKNOWN: return "UNKNOWN"; } } // JNI functions called by Android // static void AndroidController::onStatus(JNIEnv *env, jobject thiz, jint stateCode) { Q_UNUSED(env); Q_UNUSED(thiz); auto state = ConnectionState(stateCode); emit AndroidController::instance()->status(state); } // static void AndroidController::onServiceDisconnected(JNIEnv *env, jobject thiz) { Q_UNUSED(env); Q_UNUSED(thiz); emit AndroidController::instance()->serviceDisconnected(); } // static void AndroidController::onServiceError(JNIEnv *env, jobject thiz) { Q_UNUSED(env); Q_UNUSED(thiz); emit AndroidController::instance()->serviceError(); } // static void AndroidController::onVpnPermissionRejected(JNIEnv *env, jobject thiz) { Q_UNUSED(env); Q_UNUSED(thiz); emit AndroidController::instance()->vpnPermissionRejected(); } // static void AndroidController::onVpnConnected(JNIEnv *env, jobject thiz) { Q_UNUSED(env); Q_UNUSED(thiz); emit AndroidController::instance()->vpnConnected(); } // static void AndroidController::onVpnDisconnected(JNIEnv *env, jobject thiz) { Q_UNUSED(env); Q_UNUSED(thiz); emit AndroidController::instance()->vpnDisconnected(); } // static void AndroidController::onVpnReconnecting(JNIEnv *env, jobject thiz) { Q_UNUSED(env); Q_UNUSED(thiz); emit AndroidController::instance()->vpnReconnecting(); } // static void AndroidController::onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes) { Q_UNUSED(env); Q_UNUSED(thiz); emit AndroidController::instance()->statisticsUpdated((quint64) rxBytes, (quint64) txBytes); } // static void AndroidController::onFileOpened(JNIEnv *env, jobject thiz, jstring uri) { Q_UNUSED(thiz); emit AndroidController::instance()->fileOpened(AndroidUtils::convertJString(env, uri)); } // static void AndroidController::onConfigImported(JNIEnv *env, jobject thiz, jstring data) { Q_UNUSED(thiz); emit AndroidController::instance()->configImported(AndroidUtils::convertJString(env, data)); } // static bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data) { Q_UNUSED(thiz); return ImportController::decodeQrCode(AndroidUtils::convertJString(env, data)); }