#include #include #include #include "android_controller.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](bool isVpnConnected) { qDebug() << "Android event: status; connected:" << isVpnConnected; if (isWaitingStatus) { qDebug() << "Android VPN service is alive, initialization by service status"; isWaitingStatus = false; emit serviceIsAlive(isVpnConnected); } }, Qt::QueuedConnection); connect( this, &AndroidController::serviceDisconnected, this, [this]() { qDebug() << "Android event: service disconnected"; isWaitingStatus = true; emit connectionStateChanged(Vpn::ConnectionState::Unknown); }, 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::configImported, this, []() { // todo: not yet implemented qDebug() << "Transact: config import"; /*auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); QString buffer = doc.object()["config"].toString(); qDebug() << "Transact: config string" << buffer; importConfigFromOutside(buffer);*/ }, 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", "(Z)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)}, {"onStatisticsUpdate", "(JJ)V", reinterpret_cast(onStatisticsUpdate)}, {"onConfigImported", "()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 = QNativeInterface::QAndroidApplication::context(); 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()); } 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"); } // JNI functions called by Android // static void AndroidController::onStatus(JNIEnv *env, jobject thiz, jboolean isVpnConnected) { Q_UNUSED(env); Q_UNUSED(thiz); emit AndroidController::instance()->status(isVpnConnected); } // 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::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::onConfigImported(JNIEnv *env, jobject thiz) { Q_UNUSED(env); Q_UNUSED(thiz); emit AndroidController::instance()->configImported(); } // static bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data) { Q_UNUSED(thiz); const char *buffer = env->GetStringUTFChars(data, nullptr); if (!buffer) { return false; } QString code(buffer); env->ReleaseStringUTFChars(data, buffer); return ImportController::decodeQrCode(code); }