Android activity and AndroidController class refactoring
This commit is contained in:
parent
5a5ea4a018
commit
b90fad6664
13 changed files with 296 additions and 892 deletions
|
@ -91,8 +91,8 @@ void AmneziaApplication::init()
|
||||||
initControllers();
|
initControllers();
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
connect(AndroidController::instance(), &AndroidController::initialized, this,
|
connect(AndroidController::instance(), &AndroidController::serviceIsAlive, this,
|
||||||
[this](bool status, bool connected, const QDateTime &connectionDate) {
|
[this](bool connected) {
|
||||||
if (connected) {
|
if (connected) {
|
||||||
m_connectionController->onConnectionStateChanged(Vpn::ConnectionState::Connected);
|
m_connectionController->onConnectionStateChanged(Vpn::ConnectionState::Connected);
|
||||||
if (m_vpnConnection)
|
if (m_vpnConnection)
|
||||||
|
|
|
@ -1,286 +1,33 @@
|
||||||
/* 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/. */
|
|
||||||
|
|
||||||
package org.amnezia.vpn
|
package org.amnezia.vpn
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.ContentResolver
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.*
|
import java.io.FileNotFoundException
|
||||||
import android.provider.MediaStore
|
import java.io.FileOutputStream
|
||||||
import android.util.Log
|
import java.io.IOException
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import org.amnezia.vpn.VPNServiceBinder
|
|
||||||
import org.amnezia.vpn.IMPORT_COMMAND_CODE
|
|
||||||
import org.amnezia.vpn.IMPORT_ACTION_CODE
|
|
||||||
import org.amnezia.vpn.IMPORT_CONFIG_KEY
|
|
||||||
import org.qtproject.qt.android.bindings.QtActivity
|
import org.qtproject.qt.android.bindings.QtActivity
|
||||||
import java.io.*
|
|
||||||
|
|
||||||
class AmneziaActivity : org.qtproject.qt.android.bindings.QtActivity() {
|
private const val TAG = "AmneziaActivity"
|
||||||
|
|
||||||
private var configString: String? = null
|
private const val CAMERA_ACTION_CODE = 101
|
||||||
private var vpnServiceBinder: IBinder? = null
|
private const val CREATE_FILE_ACTION_CODE = 102
|
||||||
private var isBound = false
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
|
|
||||||
if (value && configString != null) {
|
class AmneziaActivity : QtActivity() {
|
||||||
sendImportConfigCommand()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val TAG = "AmneziaActivity"
|
|
||||||
|
|
||||||
private val CAMERA_ACTION_CODE = 101
|
|
||||||
private val CREATE_FILE_ACTION_CODE = 102
|
|
||||||
|
|
||||||
private var tmpFileContentToSave: String = ""
|
private var tmpFileContentToSave: String = ""
|
||||||
|
|
||||||
private val delayedCommands: ArrayList<Pair<Int, String>> = ArrayList()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private lateinit var instance: AmneziaActivity
|
|
||||||
|
|
||||||
@JvmStatic fun getInstance(): AmneziaActivity {
|
|
||||||
return instance
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic fun connectService() {
|
|
||||||
getInstance().initServiceConnection()
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic fun startQrCodeReader() {
|
|
||||||
getInstance().startQrCodeActivity()
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic fun sendToService(actionCode: Int, body: String) {
|
|
||||||
getInstance().dispatchParcel(actionCode, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic fun saveFileAs(fileContent: String, suggestedName: String) {
|
|
||||||
getInstance().saveFile(fileContent, suggestedName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
instance = this
|
|
||||||
|
|
||||||
val newIntent = intent
|
|
||||||
val newIntentAction: String? = newIntent.action
|
|
||||||
|
|
||||||
if (newIntent != null && newIntentAction != null && newIntentAction == "org.amnezia.vpn.IMPORT_CONFIG") {
|
|
||||||
configString = newIntent.getStringExtra("CONFIG")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startQrCodeActivity() {
|
private fun startQrCodeActivity() {
|
||||||
val intent = Intent(this, CameraActivity::class.java)
|
val intent = Intent(this, CameraActivity::class.java)
|
||||||
startActivityForResult(intent, CAMERA_ACTION_CODE)
|
startActivityForResult(intent, CAMERA_ACTION_CODE)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveFile(fileContent: String, suggestedName: String) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
tmpFileContentToSave = fileContent
|
|
||||||
|
|
||||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
|
||||||
type = "text/*"
|
|
||||||
putExtra(Intent.EXTRA_TITLE, suggestedName)
|
|
||||||
}
|
|
||||||
|
|
||||||
startActivityForResult(intent, CREATE_FILE_ACTION_CODE)
|
|
||||||
}
|
|
||||||
|
|
||||||
external fun handleBackButton(): Boolean
|
|
||||||
|
|
||||||
external fun onServiceMessage(actionCode: Int, body: String?)
|
|
||||||
external fun qtOnServiceConnected()
|
|
||||||
external fun qtOnServiceDisconnected()
|
|
||||||
external fun onActivityMessage(actionCode: Int, body: String?)
|
|
||||||
|
|
||||||
private fun dispatchParcel(actionCode: Int, body: String) {
|
|
||||||
if (!isBound) {
|
|
||||||
Log.d(TAG, "dispatchParcel: not bound")
|
|
||||||
delayedCommands.add(Pair(actionCode, body))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (delayedCommands.size > 0) {
|
|
||||||
for (command in delayedCommands) {
|
|
||||||
processCommand(command.first, command.second)
|
|
||||||
}
|
|
||||||
|
|
||||||
delayedCommands.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
processCommand(actionCode, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun processCommand(actionCode: Int, body: String) {
|
|
||||||
val out: Parcel = Parcel.obtain()
|
|
||||||
out.writeByteArray(body.toByteArray())
|
|
||||||
|
|
||||||
try {
|
|
||||||
vpnServiceBinder?.transact(actionCode, out, Parcel.obtain(), 0)
|
|
||||||
} catch (e: DeadObjectException) {
|
|
||||||
isBound = false
|
|
||||||
vpnServiceBinder = null
|
|
||||||
qtOnServiceDisconnected()
|
|
||||||
} catch (e: RemoteException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNewIntent(newIntent: Intent) {
|
|
||||||
super.onNewIntent(intent)
|
|
||||||
|
|
||||||
setIntent(newIntent)
|
|
||||||
|
|
||||||
val newIntentAction = newIntent.action
|
|
||||||
|
|
||||||
if (newIntent != null && newIntentAction != null && newIntentAction == INTENT_ACTION_IMPORT_CONFIG) {
|
|
||||||
configString = newIntent.getStringExtra("CONFIG")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
|
|
||||||
if (configString != null && isBound) {
|
|
||||||
sendImportConfigCommand()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sendImportConfigCommand() {
|
|
||||||
if (configString != null) {
|
|
||||||
val msg: Parcel = Parcel.obtain()
|
|
||||||
msg.writeString(configString!!)
|
|
||||||
|
|
||||||
try {
|
|
||||||
vpnServiceBinder?.transact(ACTION_IMPORT_CONFIG, msg, Parcel.obtain(), 0)
|
|
||||||
} catch (e: RemoteException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
configString = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createConnection() = object : ServiceConnection {
|
|
||||||
override fun onServiceConnected(className: ComponentName, binder: IBinder) {
|
|
||||||
vpnServiceBinder = binder
|
|
||||||
|
|
||||||
// This is called when the connection with the service has been
|
|
||||||
// established, giving us the object we can use to
|
|
||||||
// interact with the service. We are communicating with the
|
|
||||||
// service using a Messenger, so here we get a client-side
|
|
||||||
// representation of that from the raw IBinder object.
|
|
||||||
if (registerBinder()){
|
|
||||||
qtOnServiceConnected();
|
|
||||||
} else {
|
|
||||||
qtOnServiceDisconnected();
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
isBound = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onServiceDisconnected(className: ComponentName) {
|
|
||||||
vpnServiceBinder = null
|
|
||||||
isBound = false
|
|
||||||
qtOnServiceDisconnected();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var connection: ServiceConnection = createConnection()
|
|
||||||
|
|
||||||
private fun registerBinder(): Boolean {
|
|
||||||
val binder = VPNClientBinder()
|
|
||||||
val out: Parcel = Parcel.obtain()
|
|
||||||
out.writeStrongBinder(binder)
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Register our IBinder Listener
|
|
||||||
vpnServiceBinder?.transact(ACTION_REGISTER_LISTENER, out, Parcel.obtain(), 0)
|
|
||||||
return true
|
|
||||||
} catch (e: DeadObjectException) {
|
|
||||||
isBound = false
|
|
||||||
vpnServiceBinder = null
|
|
||||||
} catch (e: RemoteException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initServiceConnection() {
|
|
||||||
// We already have a connection to the service,
|
|
||||||
// just need to re-register the binder
|
|
||||||
if (isBound && vpnServiceBinder!!.isBinderAlive() && registerBinder()) {
|
|
||||||
qtOnServiceConnected()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bindService(Intent(this, AmneziaVpnService::class.java), connection, Context.BIND_AUTO_CREATE)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Move all ipc codes into a shared lib.
|
|
||||||
// this is getting out of hand.
|
|
||||||
private val PERMISSION_TRANSACTION = 1337
|
|
||||||
private val ACTION_REGISTER_LISTENER = 3
|
|
||||||
private val ACTION_RESUME_ACTIVATE = 7
|
|
||||||
private val ACTION_IMPORT_CONFIG = 11
|
|
||||||
private val EVENT_PERMISSION_REQUIRED = 6
|
|
||||||
private val EVENT_DISCONNECTED = 2
|
|
||||||
|
|
||||||
private val UI_EVENT_QR_CODE_RECEIVED = 0
|
|
||||||
|
|
||||||
fun onPermissionRequest(code: Int, data: Parcel?) {
|
|
||||||
if (code != EVENT_PERMISSION_REQUIRED) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val x = Intent()
|
|
||||||
x.readFromParcel(data)
|
|
||||||
|
|
||||||
startActivityForResult(x, PERMISSION_TRANSACTION)
|
|
||||||
}
|
|
||||||
|
|
||||||
override protected fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
||||||
if (requestCode == PERMISSION_TRANSACTION) {
|
|
||||||
// THATS US!
|
|
||||||
if (resultCode == RESULT_OK) {
|
|
||||||
// Prompt accepted, tell service to retry.
|
|
||||||
dispatchParcel(ACTION_RESUME_ACTIVATE, "")
|
|
||||||
} else {
|
|
||||||
// Tell the Client we've disconnected
|
|
||||||
onServiceMessage(EVENT_DISCONNECTED, "")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
|
|
||||||
if (requestCode == CAMERA_ACTION_CODE && resultCode == RESULT_OK) {
|
|
||||||
val extra = data?.getStringExtra("result") ?: ""
|
|
||||||
onActivityMessage(UI_EVENT_QR_CODE_RECEIVED, extra)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestCode == CREATE_FILE_ACTION_CODE && resultCode == RESULT_OK) {
|
if (requestCode == CREATE_FILE_ACTION_CODE && resultCode == RESULT_OK) {
|
||||||
data?.data?.also { uri ->
|
data?.data?.also { uri ->
|
||||||
alterDocument(uri)
|
alterDocument(uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun alterDocument(uri: Uri) {
|
private fun alterDocument(uri: Uri) {
|
||||||
|
@ -298,4 +45,58 @@ class AmneziaActivity : org.qtproject.qt.android.bindings.QtActivity() {
|
||||||
|
|
||||||
tmpFileContentToSave = ""
|
tmpFileContentToSave = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Methods called by Qt
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
fun qtAndroidControllerInitialized() {
|
||||||
|
Log.v(TAG, "Qt Android controller initialized")
|
||||||
|
Log.w(TAG, "Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun start(vpnConfig: String) {
|
||||||
|
Log.v(TAG, "Start VPN")
|
||||||
|
Log.w(TAG, "Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun stop() {
|
||||||
|
Log.v(TAG, "Stop VPN")
|
||||||
|
Log.w(TAG, "Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun saveFile(fileName: String, data: String) {
|
||||||
|
Log.v(TAG, "Save file $fileName")
|
||||||
|
// todo: refactor
|
||||||
|
tmpFileContentToSave = data
|
||||||
|
|
||||||
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||||
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
type = "text/*"
|
||||||
|
putExtra(Intent.EXTRA_TITLE, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
startActivityForResult(intent, CREATE_FILE_ACTION_CODE)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun setNotificationText(title: String, message: String, timerSec: Int) {
|
||||||
|
Log.v(TAG, "Set notification text")
|
||||||
|
Log.w(TAG, "Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun cleanupLogs() {
|
||||||
|
Log.v(TAG, "Cleanup logs")
|
||||||
|
Log.w(TAG, "Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun startQrCodeReader() {
|
||||||
|
Log.v(TAG, "Start camera")
|
||||||
|
startQrCodeActivity()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +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/. */
|
|
||||||
|
|
||||||
package org.amnezia.vpn
|
|
||||||
|
|
||||||
import android.os.Binder
|
|
||||||
import android.os.Parcel
|
|
||||||
|
|
||||||
const val permissionRequired = 6
|
|
||||||
|
|
||||||
class VPNClientBinder() : Binder() {
|
|
||||||
|
|
||||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
|
|
||||||
if (code == permissionRequired) {
|
|
||||||
AmneziaActivity.getInstance().onPermissionRequest(code, data)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
val buffer = data.createByteArray()
|
|
||||||
val stringData = buffer?.let { String(it) }
|
|
||||||
AmneziaActivity.getInstance().onServiceMessage(code, stringData)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,18 @@
|
||||||
package org.amnezia.vpn.qt
|
package org.amnezia.vpn.qt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JNI functions of the AndroidController class from android_controller.cpp,
|
||||||
|
* called by events in the Android part of the client
|
||||||
|
*/
|
||||||
object QtAndroidController {
|
object QtAndroidController {
|
||||||
|
external fun onStatus(isVpnConnected: Boolean)
|
||||||
|
external fun onServiceDisconnected()
|
||||||
|
external fun onServiceError()
|
||||||
|
|
||||||
|
external fun onVpnPermissionRejected()
|
||||||
|
external fun onVpnConnected()
|
||||||
|
external fun onVpnDisconnected()
|
||||||
|
external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long)
|
||||||
|
|
||||||
|
external fun onConfigImported()
|
||||||
}
|
}
|
|
@ -28,7 +28,6 @@ set(HEADERS ${HEADERS}
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidvpnactivity.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
|
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
|
||||||
)
|
)
|
||||||
|
@ -37,7 +36,6 @@ set(SOURCES ${SOURCES}
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidvpnactivity.cpp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,154 +1,82 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
#include <QCoreApplication>
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
#include <QJniEnvironment>
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QHostAddress>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QRandomGenerator>
|
|
||||||
#include <QTextCodec>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include "android_controller.h"
|
#include "android_controller.h"
|
||||||
#include "private/qandroidextras_p.h"
|
|
||||||
|
|
||||||
#include "androidutils.h"
|
|
||||||
#include "androidvpnactivity.h"
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
AndroidController *s_instance = nullptr;
|
AndroidController *s_instance = nullptr;
|
||||||
|
|
||||||
constexpr auto PERMISSIONHELPER_CLASS = "org/amnezia/vpn/VPNPermissionHelper";
|
constexpr auto QT_ANDROID_CONTROLLER_CLASS = "org/amnezia/vpn/qt/QtAndroidController";
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
AndroidController::AndroidController() : QObject()
|
AndroidController::AndroidController() : QObject()
|
||||||
{
|
{
|
||||||
connect(this, &AndroidController::scheduleStatusCheckSignal, this, &AndroidController::scheduleStatusCheckSlot);
|
connect(this, &AndroidController::status, this,
|
||||||
|
[this](bool isVpnConnected) {
|
||||||
s_instance = this;
|
qDebug() << "Android event: status; connected:" << isVpnConnected;
|
||||||
|
if (isWaitingInitStatus) {
|
||||||
auto activity = AndroidVPNActivity::instance();
|
qDebug() << "Android VPN service is alive, initialization by service status";
|
||||||
|
isWaitingInitStatus = false;
|
||||||
connect(
|
emit serviceIsAlive(isVpnConnected);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
activity, &AndroidVPNActivity::eventActivationError, this,
|
this, &AndroidController::serviceDisconnected, this,
|
||||||
[this](const QString &parcelBody) {
|
[this]() {
|
||||||
Q_UNUSED(parcelBody)
|
qDebug() << "Android event: service disconnected";
|
||||||
qDebug() << "Transact: error";
|
emit connectionStateChanged(Vpn::ConnectionState::Unknown);
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::Error);
|
},
|
||||||
},
|
Qt::QueuedConnection);
|
||||||
Qt::QueuedConnection);
|
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
activity, &AndroidVPNActivity::eventConfigImport, this,
|
this, &AndroidController::serviceError, this,
|
||||||
[this](const QString &parcelBody) {
|
[this]() {
|
||||||
qDebug() << "Transact: config import";
|
qDebug() << "Android event: service error";
|
||||||
auto doc = QJsonDocument::fromJson(parcelBody.toUtf8());
|
// todo: add error message
|
||||||
|
emit connectionStateChanged(Vpn::ConnectionState::Error);
|
||||||
QString buffer = doc.object()["config"].toString();
|
},
|
||||||
qDebug() << "Transact: config string" << buffer;
|
Qt::QueuedConnection);
|
||||||
importConfigFromOutside(buffer);
|
|
||||||
},
|
|
||||||
Qt::QueuedConnection);
|
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
activity, &AndroidVPNActivity::serviceDisconnected, this,
|
this, &AndroidController::vpnPermissionRejected, this,
|
||||||
[this]() {
|
[this]() {
|
||||||
qDebug() << "Transact: service disconnected";
|
qWarning() << "Android event: VPN permission rejected";
|
||||||
m_serviceConnected = false;
|
emit connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
},
|
},
|
||||||
Qt::QueuedConnection);
|
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()
|
AndroidController *AndroidController::instance()
|
||||||
|
@ -162,166 +90,160 @@ AndroidController *AndroidController::instance()
|
||||||
|
|
||||||
bool AndroidController::initialize()
|
bool AndroidController::initialize()
|
||||||
{
|
{
|
||||||
qDebug() << "Initializing";
|
qDebug() << "Initialize AndroidController";
|
||||||
|
|
||||||
|
const JNINativeMethod methods[] = {
|
||||||
|
{"onStatus", "(Z)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)},
|
||||||
|
{"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)},
|
||||||
|
{"onConfigImported", "()V", reinterpret_cast<void *>(onConfigImported)},
|
||||||
|
};
|
||||||
|
|
||||||
// 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;
|
QJniEnvironment env;
|
||||||
jclass objectClass = env->GetObjectClass(javaClass.object<jobject>());
|
bool registered = env.registerNativeMethods(QT_ANDROID_CONTROLLER_CLASS, methods,
|
||||||
env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0]));
|
sizeof(methods) / sizeof(JNINativeMethod));
|
||||||
env->DeleteLocalRef(objectClass);
|
if (!registered) {
|
||||||
|
qCritical() << "Failed native method registration";
|
||||||
AndroidVPNActivity::connectService();
|
return false;
|
||||||
|
}
|
||||||
|
qtAndroidControllerInitialized();
|
||||||
return true;
|
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";
|
qDebug() << "Call activity method:" << methodName;
|
||||||
QJniObject activity = AndroidUtils::getActivity();
|
QJniObject activity = QNativeInterface::QAndroidApplication::context();
|
||||||
auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;");
|
if (activity.isValid()) {
|
||||||
QJniObject::callStaticMethod<void>(PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V",
|
return activity.callMethod<Ret>(methodName, signature, std::forward<Args>(args)...);
|
||||||
appContext.object());
|
} else {
|
||||||
|
qCritical() << "Activity is not valid";
|
||||||
|
return defValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QJsonDocument doc(m_vpnConfig);
|
// static
|
||||||
AndroidVPNActivity::sendToService(ServiceAction::ACTION_ACTIVATE, doc.toJson());
|
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)
|
||||||
|
{
|
||||||
|
isWaitingInitStatus = false;
|
||||||
|
auto config = QJsonDocument(vpnConfig).toJson();
|
||||||
|
callActivityMethod("start", "(Ljava/lang/String;)V",
|
||||||
|
QJniObject::fromString(config).object<jstring>());
|
||||||
|
|
||||||
return NoError;
|
return NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidController::stop()
|
void AndroidController::stop()
|
||||||
{
|
{
|
||||||
qDebug() << "AndroidController::stop";
|
callActivityMethod("stop", "()V");
|
||||||
|
|
||||||
AndroidVPNActivity::sendToService(ServiceAction::ACTION_DEACTIVATE, QString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activates the tunnel that is currently set
|
void AndroidController::saveFile(const QString &fileName, const QString &data)
|
||||||
// in the VPN Service
|
|
||||||
void AndroidController::resumeStart()
|
|
||||||
{
|
{
|
||||||
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)
|
void AndroidController::setNotificationText(const QString &title, const QString &message, int timerSec)
|
||||||
{
|
{
|
||||||
QJsonObject args;
|
callActivityMethod("setNotificationText", "(Ljava/lang/String;Ljava/lang/String;I)V",
|
||||||
args["title"] = title;
|
QJniObject::fromString(title).object<jstring>(),
|
||||||
args["message"] = message;
|
QJniObject::fromString(message).object<jstring>(),
|
||||||
args["sec"] = timerSec;
|
(jint) 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidController::startQrReaderActivity()
|
void AndroidController::startQrReaderActivity()
|
||||||
{
|
{
|
||||||
AndroidVPNActivity::instance()->startQrCodeReader();
|
callActivityMethod("startQrCodeReader", "()V");
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidController::scheduleStatusCheckSlot()
|
void AndroidController::qtAndroidControllerInitialized()
|
||||||
{
|
{
|
||||||
QTimer::singleShot(1000, [this]() {
|
callActivityMethod("qtAndroidControllerInitialized", "()V");
|
||||||
if (isConnected) {
|
|
||||||
checkStatus();
|
|
||||||
emit scheduleStatusCheckSignal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const int ACTIVITY_RESULT_OK = 0xffffffff;
|
// JNI functions called by Android
|
||||||
/**
|
// static
|
||||||
* @brief Starts the Given intent in Context of the QTActivity
|
void AndroidController::onStatus(JNIEnv *env, jobject thiz, jboolean isVpnConnected)
|
||||||
* @param env
|
|
||||||
* @param intent
|
|
||||||
*/
|
|
||||||
void AndroidController::startActivityForResult(JNIEnv *env, jobject, jobject intent)
|
|
||||||
{
|
{
|
||||||
qDebug() << "start vpnPermissionHelper";
|
|
||||||
Q_UNUSED(env);
|
Q_UNUSED(env);
|
||||||
|
Q_UNUSED(thiz);
|
||||||
|
|
||||||
QtAndroidPrivate::startActivity(intent, 1337, [](int receiverRequestCode, int resultCode, const QJniObject &data) {
|
emit AndroidController::instance()->status(isVpnConnected);
|
||||||
// Currently this function just used in
|
}
|
||||||
// VPNService.kt::checkPermissions. So the result
|
|
||||||
// we're getting is if the User gave us the
|
// static
|
||||||
// Vpn.bind permission. In case of NO we should
|
void AndroidController::onServiceDisconnected(JNIEnv *env, jobject thiz)
|
||||||
// abort.
|
{
|
||||||
Q_UNUSED(receiverRequestCode);
|
Q_UNUSED(env);
|
||||||
Q_UNUSED(data);
|
Q_UNUSED(thiz);
|
||||||
|
|
||||||
AndroidController *controller = AndroidController::instance();
|
emit AndroidController::instance()->serviceDisconnected();
|
||||||
if (!controller) {
|
}
|
||||||
return;
|
|
||||||
}
|
// static
|
||||||
|
void AndroidController::onServiceError(JNIEnv *env, jobject thiz)
|
||||||
if (resultCode == ACTIVITY_RESULT_OK) {
|
{
|
||||||
qDebug() << "VPN PROMPT RESULT - Accepted";
|
Q_UNUSED(env);
|
||||||
controller->resumeStart();
|
Q_UNUSED(thiz);
|
||||||
return;
|
|
||||||
}
|
emit AndroidController::instance()->serviceError();
|
||||||
// If the request got rejected abort the current
|
}
|
||||||
// connection.
|
|
||||||
qWarning() << "VPN PROMPT RESULT - Rejected";
|
// static
|
||||||
emit controller->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
void AndroidController::onVpnPermissionRejected(JNIEnv *env, jobject thiz)
|
||||||
});
|
{
|
||||||
return;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidController::onConfigImported(JNIEnv *env, jobject thiz)
|
||||||
|
{
|
||||||
|
Q_UNUSED(env);
|
||||||
|
Q_UNUSED(thiz);
|
||||||
|
|
||||||
|
emit AndroidController::instance()->configImported();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
#ifndef ANDROID_CONTROLLER_H
|
||||||
#define ANDROID_CONTROLLER_H
|
#define ANDROID_CONTROLLER_H
|
||||||
|
|
||||||
#include <QJniEnvironment>
|
|
||||||
#include <QJniObject>
|
#include <QJniObject>
|
||||||
|
|
||||||
#include "protocols/vpnprotocol.h"
|
#include "protocols/vpnprotocol.h"
|
||||||
|
@ -20,57 +15,47 @@ public:
|
||||||
explicit AndroidController();
|
explicit AndroidController();
|
||||||
static AndroidController *instance();
|
static AndroidController *instance();
|
||||||
|
|
||||||
virtual ~AndroidController() override = default;
|
|
||||||
|
|
||||||
bool initialize();
|
bool initialize();
|
||||||
|
|
||||||
ErrorCode start();
|
ErrorCode start(const QJsonObject &vpnConfig);
|
||||||
void stop();
|
void stop();
|
||||||
void resumeStart();
|
|
||||||
|
|
||||||
void checkStatus();
|
|
||||||
void setNotificationText(const QString &title, const QString &message, int timerSec);
|
void setNotificationText(const QString &title, const QString &message, int timerSec);
|
||||||
void shareConfig(const QString &data, const QString &suggestedName);
|
void saveFile(const QString& fileName, const QString &data);
|
||||||
void setFallbackConnectedNotification();
|
|
||||||
void getBackendLogs(std::function<void(const QString &)> &&callback);
|
|
||||||
void cleanupBackendLogs();
|
|
||||||
|
|
||||||
const QJsonObject &vpnConfig() const;
|
|
||||||
void setVpnConfig(const QJsonObject &newVpnConfig);
|
|
||||||
|
|
||||||
void startQrReaderActivity();
|
void startQrReaderActivity();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void connectionStateChanged(Vpn::ConnectionState state);
|
void connectionStateChanged(Vpn::ConnectionState state);
|
||||||
|
void status(bool isVpnConnected);
|
||||||
// This signal is emitted when the controller is initialized. Note that the
|
void serviceDisconnected();
|
||||||
// VPN tunnel can be already active. In this case, "connected" should be set
|
void serviceError();
|
||||||
// to true and the "connectionDate" should be set to the activation date if
|
void vpnPermissionRejected();
|
||||||
// known.
|
void vpnConnected();
|
||||||
// If "status" is set to false, the backend service is considered unavailable.
|
void vpnDisconnected();
|
||||||
void initialized(bool status, bool connected, const QDateTime &connectionDate);
|
void statisticsUpdated(quint64 rxBytes, quint64 txBytes);
|
||||||
|
void configImported();
|
||||||
void statusUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4);
|
|
||||||
void scheduleStatusCheckSignal();
|
|
||||||
|
|
||||||
void importConfigFromOutside(QString &data);
|
void importConfigFromOutside(QString &data);
|
||||||
|
void serviceIsAlive(bool connected);
|
||||||
protected slots:
|
|
||||||
void scheduleStatusCheckSlot();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_init = false;
|
bool isWaitingInitStatus = true;
|
||||||
|
|
||||||
QJsonObject m_vpnConfig;
|
void qtAndroidControllerInitialized();
|
||||||
|
|
||||||
bool m_serviceConnected = false;
|
// JNI functions called by Android
|
||||||
std::function<void(const QString &)> m_logCallback;
|
static void onStatus(JNIEnv *env, jobject thiz, jboolean isVpnConnected);
|
||||||
|
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 onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
|
||||||
|
static void onConfigImported(JNIEnv *env, jobject thiz);
|
||||||
|
|
||||||
static void startActivityForResult(JNIEnv *env, jobject /*thiz*/, jobject intent);
|
template <typename Ret, typename ...Args>
|
||||||
|
static auto callActivityMethod(const char *methodName, const char *signature,
|
||||||
bool isConnected = false;
|
const std::function<Ret()> &defValue, Args &&...args);
|
||||||
|
template <typename ...Args>
|
||||||
void scheduleStatusCheck();
|
static void callActivityMethod(const char *methodName, const char *signature, Args &&...args);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ANDROID_CONTROLLER_H
|
#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.AmneziaActivity";
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
|
@ -1,26 +1,16 @@
|
||||||
#include <QDebug>
|
|
||||||
#include <QHostAddress>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QRandomGenerator>
|
|
||||||
#include <QTextCodec>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include "android_vpnprotocol.h"
|
#include "android_vpnprotocol.h"
|
||||||
|
|
||||||
#include "platforms/android/android_controller.h"
|
#include "platforms/android/android_controller.h"
|
||||||
|
|
||||||
|
|
||||||
AndroidVpnProtocol::AndroidVpnProtocol(Proto protocol, const QJsonObject &configuration, QObject* parent)
|
AndroidVpnProtocol::AndroidVpnProtocol(Proto protocol, const QJsonObject &configuration, QObject* parent)
|
||||||
: VpnProtocol(configuration, parent),
|
: VpnProtocol(configuration, parent)
|
||||||
m_protocol(protocol)
|
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
ErrorCode AndroidVpnProtocol::start()
|
ErrorCode AndroidVpnProtocol::start()
|
||||||
{
|
{
|
||||||
AndroidController::instance()->setVpnConfig(m_rawConfig);
|
qDebug() << "AndroidVpnProtocol::start()";
|
||||||
return AndroidController::instance()->start();
|
return AndroidController::instance()->start(m_rawConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidVpnProtocol::stop()
|
void AndroidVpnProtocol::stop()
|
||||||
|
@ -29,11 +19,8 @@ void AndroidVpnProtocol::stop()
|
||||||
AndroidController::instance()->stop();
|
AndroidController::instance()->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidVpnProtocol::connectionDataUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4)
|
void AndroidVpnProtocol::connectionDataUpdated(quint64 rxBytes, quint64 txBytes)
|
||||||
{
|
{
|
||||||
quint64 rxBytes = totalRx.toLongLong();
|
|
||||||
quint64 txBytes = totalTx.toLongLong();
|
|
||||||
|
|
||||||
setBytesChanged(rxBytes, txBytes);
|
setBytesChanged(rxBytes, txBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
#define ANDROID_VPNPROTOCOL_H
|
#define ANDROID_VPNPROTOCOL_H
|
||||||
|
|
||||||
#include "vpnprotocol.h"
|
#include "vpnprotocol.h"
|
||||||
#include "protocols/protocols_defs.h"
|
|
||||||
|
|
||||||
using namespace amnezia;
|
using namespace amnezia;
|
||||||
|
|
||||||
|
@ -12,24 +11,14 @@ class AndroidVpnProtocol : public VpnProtocol
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AndroidVpnProtocol(Proto protocol, const QJsonObject& configuration, QObject* parent = nullptr);
|
explicit AndroidVpnProtocol(Proto protocol, const QJsonObject& configuration, QObject* parent = nullptr);
|
||||||
virtual ~AndroidVpnProtocol() override = default;
|
~AndroidVpnProtocol() override = default;
|
||||||
|
|
||||||
ErrorCode start() override;
|
ErrorCode start() override;
|
||||||
void stop() override;
|
void stop() override;
|
||||||
|
|
||||||
signals:
|
|
||||||
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void connectionDataUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4);
|
void connectionDataUpdated(quint64 rxBytes, quint64 txBytes);
|
||||||
|
|
||||||
protected slots:
|
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
Proto m_protocol;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ANDROID_VPNPROTOCOL_H
|
#endif // ANDROID_VPNPROTOCOL_H
|
||||||
|
|
|
@ -27,7 +27,7 @@ SystemController::SystemController(const std::shared_ptr<Settings> &settings, QO
|
||||||
void SystemController::saveFile(QString fileName, const QString &data)
|
void SystemController::saveFile(QString fileName, const QString &data)
|
||||||
{
|
{
|
||||||
#if defined Q_OS_ANDROID
|
#if defined Q_OS_ANDROID
|
||||||
AndroidController::instance()->shareConfig(data, fileName);
|
AndroidController::instance()->saveFile(fileName, data);
|
||||||
return;
|
return;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -409,7 +409,7 @@ void VpnConnection::createAndroidConnections(DockerContainer container)
|
||||||
|
|
||||||
connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol,
|
connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol,
|
||||||
&AndroidVpnProtocol::setConnectionState);
|
&AndroidVpnProtocol::setConnectionState);
|
||||||
connect(AndroidController::instance(), &AndroidController::statusUpdated, androidVpnProtocol,
|
connect(AndroidController::instance(), &AndroidController::statisticsUpdated, androidVpnProtocol,
|
||||||
&AndroidVpnProtocol::connectionDataUpdated);
|
&AndroidVpnProtocol::connectionDataUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue