Merge branch 'dev' into feature/killswitch
This commit is contained in:
commit
65a04799ef
294 changed files with 9840 additions and 85333 deletions
|
@ -1,154 +1,87 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <QDebug>
|
||||
#include <QHostAddress>
|
||||
#include <QJsonArray>
|
||||
#include <QCoreApplication>
|
||||
#include <QJniEnvironment>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QRandomGenerator>
|
||||
#include <QTextCodec>
|
||||
#include <QTimer>
|
||||
|
||||
#include "android_controller.h"
|
||||
#include "private/qandroidextras_p.h"
|
||||
|
||||
#include "androidutils.h"
|
||||
#include "androidvpnactivity.h"
|
||||
#include "ui/controllers/importController.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
AndroidController *s_instance = nullptr;
|
||||
|
||||
constexpr auto PERMISSIONHELPER_CLASS = "org/amnezia/vpn/qt/VPNPermissionHelper";
|
||||
constexpr auto QT_ANDROID_CONTROLLER_CLASS = "org/amnezia/vpn/qt/QtAndroidController";
|
||||
} // namespace
|
||||
|
||||
AndroidController::AndroidController() : QObject()
|
||||
{
|
||||
connect(this, &AndroidController::scheduleStatusCheckSignal, this, &AndroidController::scheduleStatusCheckSlot);
|
||||
|
||||
s_instance = this;
|
||||
|
||||
auto activity = AndroidVPNActivity::instance();
|
||||
|
||||
connect(
|
||||
activity, &AndroidVPNActivity::serviceConnected, this,
|
||||
[]() {
|
||||
qDebug() << "Transact: service connected";
|
||||
AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, "");
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(
|
||||
activity, &AndroidVPNActivity::eventInitialized, this,
|
||||
[this](const QString &parcelBody) {
|
||||
// We might get multiple Init events as widgets, or fragments
|
||||
// might query this.
|
||||
if (m_init) {
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Transact: init";
|
||||
|
||||
m_init = true;
|
||||
|
||||
auto doc = QJsonDocument::fromJson(parcelBody.toUtf8());
|
||||
qlonglong time = doc.object()["time"].toVariant().toLongLong();
|
||||
|
||||
isConnected = doc.object()["connected"].toBool();
|
||||
|
||||
if (isConnected) {
|
||||
emit scheduleStatusCheckSignal();
|
||||
}
|
||||
|
||||
emit initialized(true, isConnected, time > 0 ? QDateTime::fromMSecsSinceEpoch(time) : QDateTime());
|
||||
|
||||
setFallbackConnectedNotification();
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(
|
||||
activity, &AndroidVPNActivity::eventConnected, this,
|
||||
[this](const QString &parcelBody) {
|
||||
Q_UNUSED(parcelBody);
|
||||
qDebug() << "Transact: connected";
|
||||
|
||||
if (!isConnected) {
|
||||
emit scheduleStatusCheckSignal();
|
||||
}
|
||||
|
||||
isConnected = true;
|
||||
|
||||
emit connectionStateChanged(Vpn::ConnectionState::Connected);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(
|
||||
activity, &AndroidVPNActivity::eventDisconnected, this,
|
||||
[this]() {
|
||||
qDebug() << "Transact: disconnected";
|
||||
|
||||
isConnected = false;
|
||||
|
||||
emit connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(
|
||||
activity, &AndroidVPNActivity::eventStatisticUpdate, this,
|
||||
[this](const QString &parcelBody) {
|
||||
auto doc = QJsonDocument::fromJson(parcelBody.toUtf8());
|
||||
|
||||
QString rx = doc.object()["rx_bytes"].toString();
|
||||
QString tx = doc.object()["tx_bytes"].toString();
|
||||
QString endpoint = doc.object()["endpoint"].toString();
|
||||
QString deviceIPv4 = doc.object()["deviceIpv4"].toString();
|
||||
|
||||
emit statusUpdated(rx, tx, endpoint, deviceIPv4);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(
|
||||
activity, &AndroidVPNActivity::eventBackendLogs, this,
|
||||
[this](const QString &parcelBody) {
|
||||
qDebug() << "Transact: backend logs";
|
||||
|
||||
QString buffer = parcelBody.toUtf8();
|
||||
if (m_logCallback) {
|
||||
m_logCallback(buffer);
|
||||
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(
|
||||
activity, &AndroidVPNActivity::eventActivationError, this,
|
||||
[this](const QString &parcelBody) {
|
||||
Q_UNUSED(parcelBody)
|
||||
qDebug() << "Transact: error";
|
||||
emit connectionStateChanged(Vpn::ConnectionState::Error);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
this, &AndroidController::serviceDisconnected, this,
|
||||
[this]() {
|
||||
qDebug() << "Android event: service disconnected";
|
||||
isWaitingStatus = true;
|
||||
emit connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(
|
||||
activity, &AndroidVPNActivity::eventConfigImport, this,
|
||||
[this](const QString &parcelBody) {
|
||||
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);
|
||||
this, &AndroidController::serviceError, this,
|
||||
[this]() {
|
||||
qDebug() << "Android event: service error";
|
||||
// todo: add error message
|
||||
emit connectionStateChanged(Vpn::ConnectionState::Error);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(
|
||||
activity, &AndroidVPNActivity::serviceDisconnected, this,
|
||||
[this]() {
|
||||
qDebug() << "Transact: service disconnected";
|
||||
m_serviceConnected = false;
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
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()
|
||||
|
@ -162,166 +95,223 @@ AndroidController *AndroidController::instance()
|
|||
|
||||
bool AndroidController::initialize()
|
||||
{
|
||||
qDebug() << "Initializing";
|
||||
qDebug() << "Initialize AndroidController";
|
||||
|
||||
const JNINativeMethod methods[] = {
|
||||
{"onStatus", "(I)V", reinterpret_cast<void *>(onStatus)},
|
||||
{"onServiceDisconnected", "()V", reinterpret_cast<void *>(onServiceDisconnected)},
|
||||
{"onServiceError", "()V", reinterpret_cast<void *>(onServiceError)},
|
||||
{"onVpnPermissionRejected", "()V", reinterpret_cast<void *>(onVpnPermissionRejected)},
|
||||
{"onVpnConnected", "()V", reinterpret_cast<void *>(onVpnConnected)},
|
||||
{"onVpnDisconnected", "()V", reinterpret_cast<void *>(onVpnDisconnected)},
|
||||
{"onVpnReconnecting", "()V", reinterpret_cast<void *>(onVpnReconnecting)},
|
||||
{"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)},
|
||||
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
|
||||
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)}
|
||||
};
|
||||
|
||||
// Hook in the native implementation for startActivityForResult into the JNI
|
||||
JNINativeMethod methods[] { { "startActivityForResult", "(Landroid/content/Intent;)V",
|
||||
reinterpret_cast<void *>(startActivityForResult) } };
|
||||
QJniObject javaClass(PERMISSIONHELPER_CLASS);
|
||||
QJniEnvironment env;
|
||||
jclass objectClass = env->GetObjectClass(javaClass.object<jobject>());
|
||||
env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0]));
|
||||
env->DeleteLocalRef(objectClass);
|
||||
|
||||
AndroidVPNActivity::connectService();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ErrorCode AndroidController::start()
|
||||
// static
|
||||
template <typename Ret, typename ...Args>
|
||||
auto AndroidController::callActivityMethod(const char *methodName, const char *signature,
|
||||
const std::function<Ret()> &defValue, Args &&...args)
|
||||
{
|
||||
qDebug() << "Prompting for VPN permission";
|
||||
QJniObject activity = AndroidUtils::getActivity();
|
||||
auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;");
|
||||
QJniObject::callStaticMethod<void>(PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V",
|
||||
appContext.object());
|
||||
qDebug() << "Call activity method:" << methodName;
|
||||
QJniObject activity = QNativeInterface::QAndroidApplication::context();
|
||||
if (activity.isValid()) {
|
||||
return activity.callMethod<Ret>(methodName, signature, std::forward<Args>(args)...);
|
||||
} else {
|
||||
qCritical() << "Activity is not valid";
|
||||
return defValue();
|
||||
}
|
||||
}
|
||||
|
||||
QJsonDocument doc(m_vpnConfig);
|
||||
AndroidVPNActivity::sendToService(ServiceAction::ACTION_ACTIVATE, doc.toJson());
|
||||
// static
|
||||
template <typename ...Args>
|
||||
void AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args)
|
||||
{
|
||||
callActivityMethod<void>(methodName, signature, [] {}, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
ErrorCode AndroidController::start(const QJsonObject &vpnConfig)
|
||||
{
|
||||
isWaitingStatus = false;
|
||||
auto config = QJsonDocument(vpnConfig).toJson();
|
||||
callActivityMethod("start", "(Ljava/lang/String;)V",
|
||||
QJniObject::fromString(config).object<jstring>());
|
||||
|
||||
return NoError;
|
||||
}
|
||||
|
||||
void AndroidController::stop()
|
||||
{
|
||||
qDebug() << "AndroidController::stop";
|
||||
|
||||
AndroidVPNActivity::sendToService(ServiceAction::ACTION_DEACTIVATE, QString());
|
||||
callActivityMethod("stop", "()V");
|
||||
}
|
||||
|
||||
// Activates the tunnel that is currently set
|
||||
// in the VPN Service
|
||||
void AndroidController::resumeStart()
|
||||
void AndroidController::saveFile(const QString &fileName, const QString &data)
|
||||
{
|
||||
AndroidVPNActivity::sendToService(ServiceAction::ACTION_RESUME_ACTIVATE, QString());
|
||||
callActivityMethod("saveFile", "(Ljava/lang/String;Ljava/lang/String;)V",
|
||||
QJniObject::fromString(fileName).object<jstring>(),
|
||||
QJniObject::fromString(data).object<jstring>());
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the current notification text that is shown
|
||||
*/
|
||||
void AndroidController::setNotificationText(const QString &title, const QString &message, int timerSec)
|
||||
{
|
||||
QJsonObject args;
|
||||
args["title"] = title;
|
||||
args["message"] = message;
|
||||
args["sec"] = timerSec;
|
||||
QJsonDocument doc(args);
|
||||
|
||||
AndroidVPNActivity::sendToService(ServiceAction::ACTION_SET_NOTIFICATION_TEXT, doc.toJson());
|
||||
}
|
||||
|
||||
void AndroidController::shareConfig(const QString &configContent, const QString &suggestedName)
|
||||
{
|
||||
AndroidVPNActivity::saveFileAs(configContent, suggestedName);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets fallback Notification text that should be shown in case the VPN
|
||||
* switches into the Connected state without the app open
|
||||
* e.g via always-on vpn
|
||||
*/
|
||||
void AndroidController::setFallbackConnectedNotification()
|
||||
{
|
||||
QJsonObject args;
|
||||
args["title"] = tr("AmneziaVPN");
|
||||
//% "Ready for you to connect"
|
||||
//: Refers to the app - which is currently running the background and waiting
|
||||
args["message"] = tr("VPN Connected");
|
||||
QJsonDocument doc(args);
|
||||
|
||||
AndroidVPNActivity::sendToService(ServiceAction::ACTION_SET_NOTIFICATION_FALLBACK, doc.toJson());
|
||||
}
|
||||
|
||||
void AndroidController::checkStatus()
|
||||
{
|
||||
AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, QString());
|
||||
}
|
||||
|
||||
void AndroidController::getBackendLogs(std::function<void(const QString &)> &&a_callback)
|
||||
{
|
||||
qDebug() << "get logs";
|
||||
|
||||
m_logCallback = std::move(a_callback);
|
||||
|
||||
AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_GET_LOG, QString());
|
||||
}
|
||||
|
||||
void AndroidController::cleanupBackendLogs()
|
||||
{
|
||||
qDebug() << "cleanup logs";
|
||||
|
||||
AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_CLEANUP_LOG, QString());
|
||||
}
|
||||
|
||||
const QJsonObject &AndroidController::vpnConfig() const
|
||||
{
|
||||
return m_vpnConfig;
|
||||
}
|
||||
|
||||
void AndroidController::setVpnConfig(const QJsonObject &newVpnConfig)
|
||||
{
|
||||
m_vpnConfig = newVpnConfig;
|
||||
callActivityMethod("setNotificationText", "(Ljava/lang/String;Ljava/lang/String;I)V",
|
||||
QJniObject::fromString(title).object<jstring>(),
|
||||
QJniObject::fromString(message).object<jstring>(),
|
||||
(jint) timerSec);
|
||||
}
|
||||
|
||||
void AndroidController::startQrReaderActivity()
|
||||
{
|
||||
AndroidVPNActivity::instance()->startQrCodeReader();
|
||||
callActivityMethod("startQrCodeReader", "()V");
|
||||
}
|
||||
|
||||
void AndroidController::scheduleStatusCheckSlot()
|
||||
void AndroidController::qtAndroidControllerInitialized()
|
||||
{
|
||||
QTimer::singleShot(1000, [this]() {
|
||||
if (isConnected) {
|
||||
checkStatus();
|
||||
emit scheduleStatusCheckSignal();
|
||||
}
|
||||
});
|
||||
callActivityMethod("qtAndroidControllerInitialized", "()V");
|
||||
}
|
||||
|
||||
const int ACTIVITY_RESULT_OK = 0xffffffff;
|
||||
/**
|
||||
* @brief Starts the Given intent in Context of the QTActivity
|
||||
* @param env
|
||||
* @param intent
|
||||
*/
|
||||
void AndroidController::startActivityForResult(JNIEnv *env, jobject, jobject intent)
|
||||
// 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)
|
||||
{
|
||||
qDebug() << "start vpnPermissionHelper";
|
||||
Q_UNUSED(env);
|
||||
Q_UNUSED(thiz);
|
||||
|
||||
QtAndroidPrivate::startActivity(intent, 1337, [](int receiverRequestCode, int resultCode, const QJniObject &data) {
|
||||
// Currently this function just used in
|
||||
// VPNService.kt::checkPermissions. So the result
|
||||
// we're getting is if the User gave us the
|
||||
// Vpn.bind permission. In case of NO we should
|
||||
// abort.
|
||||
Q_UNUSED(receiverRequestCode);
|
||||
Q_UNUSED(data);
|
||||
auto state = ConnectionState(stateCode);
|
||||
|
||||
AndroidController *controller = AndroidController::instance();
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (resultCode == ACTIVITY_RESULT_OK) {
|
||||
qDebug() << "VPN PROMPT RESULT - Accepted";
|
||||
controller->resumeStart();
|
||||
return;
|
||||
}
|
||||
// If the request got rejected abort the current
|
||||
// connection.
|
||||
qWarning() << "VPN PROMPT RESULT - Rejected";
|
||||
emit controller->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
});
|
||||
return;
|
||||
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::onConfigImported(JNIEnv *env, jobject thiz, jstring data)
|
||||
{
|
||||
Q_UNUSED(env);
|
||||
Q_UNUSED(thiz);
|
||||
|
||||
const char *buffer = env->GetStringUTFChars(data, nullptr);
|
||||
if (!buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString config(buffer);
|
||||
env->ReleaseStringUTFChars(data, buffer);
|
||||
|
||||
emit AndroidController::instance()->configImported(config);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef ANDROID_CONTROLLER_H
|
||||
#define ANDROID_CONTROLLER_H
|
||||
|
||||
#include <QJniEnvironment>
|
||||
#include <QJniObject>
|
||||
|
||||
#include "protocols/vpnprotocol.h"
|
||||
|
@ -20,57 +15,63 @@ public:
|
|||
explicit AndroidController();
|
||||
static AndroidController *instance();
|
||||
|
||||
virtual ~AndroidController() override = default;
|
||||
|
||||
bool initialize();
|
||||
|
||||
ErrorCode start();
|
||||
// keep synchronized with org.amnezia.vpn.protocol.ProtocolState
|
||||
enum class ConnectionState {
|
||||
CONNECTED,
|
||||
CONNECTING,
|
||||
DISCONNECTED,
|
||||
DISCONNECTING,
|
||||
RECONNECTING,
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
ErrorCode start(const QJsonObject &vpnConfig);
|
||||
void stop();
|
||||
void resumeStart();
|
||||
|
||||
void checkStatus();
|
||||
void setNotificationText(const QString &title, const QString &message, int timerSec);
|
||||
void shareConfig(const QString &data, const QString &suggestedName);
|
||||
void setFallbackConnectedNotification();
|
||||
void getBackendLogs(std::function<void(const QString &)> &&callback);
|
||||
void cleanupBackendLogs();
|
||||
|
||||
const QJsonObject &vpnConfig() const;
|
||||
void setVpnConfig(const QJsonObject &newVpnConfig);
|
||||
|
||||
void saveFile(const QString& fileName, const QString &data);
|
||||
void startQrReaderActivity();
|
||||
|
||||
signals:
|
||||
void connectionStateChanged(Vpn::ConnectionState state);
|
||||
|
||||
// This signal is emitted when the controller is initialized. Note that the
|
||||
// VPN tunnel can be already active. In this case, "connected" should be set
|
||||
// to true and the "connectionDate" should be set to the activation date if
|
||||
// known.
|
||||
// If "status" is set to false, the backend service is considered unavailable.
|
||||
void initialized(bool status, bool connected, const QDateTime &connectionDate);
|
||||
|
||||
void statusUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4);
|
||||
void scheduleStatusCheckSignal();
|
||||
|
||||
void importConfigFromOutside(QString &data);
|
||||
|
||||
protected slots:
|
||||
void scheduleStatusCheckSlot();
|
||||
void status(ConnectionState state);
|
||||
void serviceDisconnected();
|
||||
void serviceError();
|
||||
void vpnPermissionRejected();
|
||||
void vpnConnected();
|
||||
void vpnDisconnected();
|
||||
void vpnReconnecting();
|
||||
void statisticsUpdated(quint64 rxBytes, quint64 txBytes);
|
||||
void configImported(QString config);
|
||||
void importConfigFromOutside(QString config);
|
||||
void initConnectionState(Vpn::ConnectionState state);
|
||||
|
||||
private:
|
||||
bool m_init = false;
|
||||
bool isWaitingStatus = true;
|
||||
|
||||
QJsonObject m_vpnConfig;
|
||||
void qtAndroidControllerInitialized();
|
||||
|
||||
bool m_serviceConnected = false;
|
||||
std::function<void(const QString &)> m_logCallback;
|
||||
static Vpn::ConnectionState convertState(ConnectionState state);
|
||||
static QString textConnectionState(ConnectionState state);
|
||||
|
||||
static void startActivityForResult(JNIEnv *env, jobject /*thiz*/, jobject intent);
|
||||
// JNI functions called by Android
|
||||
static void onStatus(JNIEnv *env, jobject thiz, jint stateCode);
|
||||
static void onServiceDisconnected(JNIEnv *env, jobject thiz);
|
||||
static void onServiceError(JNIEnv *env, jobject thiz);
|
||||
static void onVpnPermissionRejected(JNIEnv *env, jobject thiz);
|
||||
static void onVpnConnected(JNIEnv *env, jobject thiz);
|
||||
static void onVpnDisconnected(JNIEnv *env, jobject thiz);
|
||||
static void onVpnReconnecting(JNIEnv *env, jobject thiz);
|
||||
static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
|
||||
static void onConfigImported(JNIEnv *env, jobject thiz, jstring data);
|
||||
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
|
||||
|
||||
bool isConnected = false;
|
||||
|
||||
void scheduleStatusCheck();
|
||||
template <typename Ret, typename ...Args>
|
||||
static auto callActivityMethod(const char *methodName, const char *signature,
|
||||
const std::function<Ret()> &defValue, Args &&...args);
|
||||
template <typename ...Args>
|
||||
static void callActivityMethod(const char *methodName, const char *signature, Args &&...args);
|
||||
};
|
||||
|
||||
#endif // ANDROID_CONTROLLER_H
|
||||
|
|
|
@ -1,162 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "androidvpnactivity.h"
|
||||
|
||||
#include <QJniEnvironment>
|
||||
#include <QJniObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "androidutils.h"
|
||||
#include "jni.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
AndroidVPNActivity *s_instance = nullptr;
|
||||
constexpr auto CLASSNAME = "org.amnezia.vpn.qt.VPNActivity";
|
||||
}
|
||||
|
||||
AndroidVPNActivity::AndroidVPNActivity()
|
||||
{
|
||||
AndroidUtils::runOnAndroidThreadAsync([]() {
|
||||
JNINativeMethod methods[] {
|
||||
{ "handleBackButton", "()Z", reinterpret_cast<bool *>(handleBackButton) },
|
||||
{ "onServiceMessage", "(ILjava/lang/String;)V", reinterpret_cast<void *>(onServiceMessage) },
|
||||
{ "qtOnServiceConnected", "()V", reinterpret_cast<void *>(onServiceConnected) },
|
||||
{ "qtOnServiceDisconnected", "()V", reinterpret_cast<void *>(onServiceDisconnected) },
|
||||
{ "onActivityMessage", "(ILjava/lang/String;)V", reinterpret_cast<void *>(onAndroidVpnActivityMessage) }
|
||||
};
|
||||
|
||||
QJniObject javaClass(CLASSNAME);
|
||||
QJniEnvironment env;
|
||||
jclass objectClass = env->GetObjectClass(javaClass.object<jobject>());
|
||||
env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0]));
|
||||
env->DeleteLocalRef(objectClass);
|
||||
});
|
||||
}
|
||||
|
||||
void AndroidVPNActivity::maybeInit()
|
||||
{
|
||||
if (s_instance == nullptr) {
|
||||
s_instance = new AndroidVPNActivity();
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
bool AndroidVPNActivity::handleBackButton(JNIEnv *env, jobject thiz)
|
||||
{
|
||||
Q_UNUSED(env);
|
||||
Q_UNUSED(thiz);
|
||||
}
|
||||
|
||||
void AndroidVPNActivity::connectService()
|
||||
{
|
||||
QJniObject::callStaticMethod<void>(CLASSNAME, "connectService", "()V");
|
||||
}
|
||||
|
||||
void AndroidVPNActivity::startQrCodeReader()
|
||||
{
|
||||
QJniObject::callStaticMethod<void>(CLASSNAME, "startQrCodeReader", "()V");
|
||||
}
|
||||
|
||||
void AndroidVPNActivity::saveFileAs(QString fileContent, QString suggestedFilename)
|
||||
{
|
||||
QJniObject::callStaticMethod<void>(CLASSNAME, "saveFileAs", "(Ljava/lang/String;Ljava/lang/String;)V",
|
||||
QJniObject::fromString(fileContent).object<jstring>(),
|
||||
QJniObject::fromString(suggestedFilename).object<jstring>());
|
||||
}
|
||||
|
||||
// static
|
||||
AndroidVPNActivity *AndroidVPNActivity::instance()
|
||||
{
|
||||
if (s_instance == nullptr) {
|
||||
AndroidVPNActivity::maybeInit();
|
||||
}
|
||||
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
// static
|
||||
void AndroidVPNActivity::sendToService(ServiceAction type, const QString &data)
|
||||
{
|
||||
int messageType = (int)type;
|
||||
|
||||
QJniObject::callStaticMethod<void>(CLASSNAME, "sendToService", "(ILjava/lang/String;)V",
|
||||
static_cast<int>(messageType), QJniObject::fromString(data).object<jstring>());
|
||||
}
|
||||
|
||||
// static
|
||||
void AndroidVPNActivity::onServiceMessage(JNIEnv *env, jobject thiz, jint messageType, jstring body)
|
||||
{
|
||||
Q_UNUSED(thiz);
|
||||
const char *buffer = env->GetStringUTFChars(body, nullptr);
|
||||
if (!buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString parcelBody(buffer);
|
||||
env->ReleaseStringUTFChars(body, buffer);
|
||||
AndroidUtils::dispatchToMainThread([messageType, parcelBody] {
|
||||
AndroidVPNActivity::instance()->handleServiceMessage(messageType, parcelBody);
|
||||
});
|
||||
}
|
||||
|
||||
void AndroidVPNActivity::handleServiceMessage(int code, const QString &data)
|
||||
{
|
||||
auto mode = (ServiceEvents)code;
|
||||
|
||||
switch (mode) {
|
||||
case ServiceEvents::EVENT_INIT: emit eventInitialized(data); break;
|
||||
case ServiceEvents::EVENT_CONNECTED: emit eventConnected(data); break;
|
||||
case ServiceEvents::EVENT_DISCONNECTED: emit eventDisconnected(data); break;
|
||||
case ServiceEvents::EVENT_STATISTIC_UPDATE: emit eventStatisticUpdate(data); break;
|
||||
case ServiceEvents::EVENT_BACKEND_LOGS: emit eventBackendLogs(data); break;
|
||||
case ServiceEvents::EVENT_ACTIVATION_ERROR: emit eventActivationError(data); break;
|
||||
case ServiceEvents::EVENT_CONFIG_IMPORT: emit eventConfigImport(data); break;
|
||||
default: Q_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidVPNActivity::handleActivityMessage(int code, const QString &data)
|
||||
{
|
||||
auto mode = (UIEvents)code;
|
||||
|
||||
switch (mode) {
|
||||
case UIEvents::QR_CODED_DECODED: emit eventQrCodeReceived(data); break;
|
||||
default: Q_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidVPNActivity::onServiceConnected(JNIEnv *env, jobject thiz)
|
||||
{
|
||||
Q_UNUSED(env);
|
||||
Q_UNUSED(thiz);
|
||||
|
||||
emit AndroidVPNActivity::instance()->serviceConnected();
|
||||
}
|
||||
|
||||
void AndroidVPNActivity::onServiceDisconnected(JNIEnv *env, jobject thiz)
|
||||
{
|
||||
Q_UNUSED(env);
|
||||
Q_UNUSED(thiz);
|
||||
|
||||
emit AndroidVPNActivity::instance()->serviceDisconnected();
|
||||
}
|
||||
|
||||
void AndroidVPNActivity::onAndroidVpnActivityMessage(JNIEnv *env, jobject thiz, jint messageType, jstring message)
|
||||
{
|
||||
Q_UNUSED(thiz);
|
||||
const char *buffer = env->GetStringUTFChars(message, nullptr);
|
||||
if (!buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString parcelBody(buffer);
|
||||
env->ReleaseStringUTFChars(message, buffer);
|
||||
|
||||
AndroidUtils::dispatchToMainThread([messageType, parcelBody] {
|
||||
AndroidVPNActivity::instance()->handleActivityMessage(messageType, parcelBody);
|
||||
});
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef ANDROIDVPNACTIVITY_H
|
||||
#define ANDROIDVPNACTIVITY_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "jni.h"
|
||||
|
||||
// Binder Codes for VPNServiceBinder
|
||||
// See also - VPNServiceBinder.kt
|
||||
// Actions that are Requestable
|
||||
enum ServiceAction {
|
||||
// Activate the vpn. Body requires a json wg-conf
|
||||
ACTION_ACTIVATE = 1,
|
||||
// Deactivate the vpn. Body is empty
|
||||
ACTION_DEACTIVATE = 2,
|
||||
// Register an IBinder to receive events body is an Ibinder
|
||||
ACTION_REGISTERLISTENER = 3,
|
||||
// Requests an EVENT_STATISTIC_UPDATE to be send
|
||||
ACTION_REQUEST_STATISTIC = 4,
|
||||
ACTION_REQUEST_GET_LOG = 5,
|
||||
// Requests to clean up the internal log
|
||||
ACTION_REQUEST_CLEANUP_LOG = 6,
|
||||
// Retry activation using the last config
|
||||
// Used when the activation is aborted for VPN-Permission prompt
|
||||
ACTION_RESUME_ACTIVATE = 7,
|
||||
// Sets the current notification text.
|
||||
// Does nothing if there is no notification
|
||||
ACTION_SET_NOTIFICATION_TEXT = 8,
|
||||
// Sets the fallback text if the OS triggered the VPN-Service
|
||||
// to show a notification
|
||||
ACTION_SET_NOTIFICATION_FALLBACK = 9,
|
||||
// Share used config
|
||||
ACTION_SHARE_CONFIG = 10,
|
||||
};
|
||||
typedef enum ServiceAction ServiceAction;
|
||||
|
||||
// Event Types that will be Dispatched after registration
|
||||
// Kotlin codes in the VPNServiceBinder.kt
|
||||
enum ServiceEvents {
|
||||
// The Service has Accepted our Binder
|
||||
// Responds with the current status of the vpn.
|
||||
EVENT_INIT = 0,
|
||||
// WG-Go has enabled the adapter (empty response)
|
||||
EVENT_CONNECTED = 1,
|
||||
// WG-Go has disabled the adapter (empty response)
|
||||
EVENT_DISCONNECTED = 2,
|
||||
// Contains the Current transferred bytes to endpoint x.
|
||||
EVENT_STATISTIC_UPDATE = 3,
|
||||
EVENT_BACKEND_LOGS = 4,
|
||||
// An Error happened during activation
|
||||
// Contains the error message
|
||||
EVENT_ACTIVATION_ERROR = 5,
|
||||
EVENT_NEED_PERMISSION = 6,
|
||||
// Import of existing config
|
||||
EVENT_CONFIG_IMPORT = 7,
|
||||
};
|
||||
typedef enum ServiceEvents ServiceEvents;
|
||||
|
||||
enum UIEvents {
|
||||
QR_CODED_DECODED = 0,
|
||||
};
|
||||
typedef enum UIEvents UIEvents;
|
||||
|
||||
class AndroidVPNActivity : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static void maybeInit();
|
||||
static AndroidVPNActivity* instance();
|
||||
static bool handleBackButton(JNIEnv* env, jobject thiz);
|
||||
static void sendToService(ServiceAction type, const QString& data);
|
||||
static void connectService();
|
||||
static void startQrCodeReader();
|
||||
static void saveFileAs(QString fileContent, QString suggestedFilename);
|
||||
|
||||
signals:
|
||||
void serviceConnected();
|
||||
void serviceDisconnected();
|
||||
void eventInitialized(const QString& data);
|
||||
void eventConnected(const QString& data);
|
||||
void eventDisconnected(const QString& data);
|
||||
void eventStatisticUpdate(const QString& data);
|
||||
void eventBackendLogs(const QString& data);
|
||||
void eventActivationError(const QString& data);
|
||||
void eventConfigImport(const QString& data);
|
||||
void eventQrCodeReceived(const QString& data);
|
||||
|
||||
private:
|
||||
AndroidVPNActivity();
|
||||
|
||||
static void onServiceMessage(JNIEnv* env, jobject thiz, jint messageType, jstring body);
|
||||
static void onServiceConnected(JNIEnv* env, jobject thiz);
|
||||
static void onServiceDisconnected(JNIEnv* env, jobject thiz);
|
||||
static void onAndroidVpnActivityMessage(JNIEnv* env, jobject thiz, jint messageType, jstring message);
|
||||
void handleServiceMessage(int code, const QString& data);
|
||||
void handleActivityMessage(int code, const QString& data);
|
||||
};
|
||||
|
||||
#endif // ANDROIDVPNACTIVITY_H
|
Loading…
Add table
Add a link
Reference in a new issue