diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 27d17c0dc..397dc18a5 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -255,7 +255,7 @@ jobs:
host: 'linux'
target: 'desktop'
arch: 'gcc_64'
- modules: 'qtremoteobjects qt5compat'
+ modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
@@ -268,7 +268,7 @@ jobs:
host: 'linux'
target: 'android'
arch: ${{ matrix.arch }}
- modules: 'qtremoteobjects qt5compat'
+ modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
@@ -287,17 +287,19 @@ jobs:
uses: actions/setup-java@v3
with:
distribution: 'temurin'
- java-version: '8'
+ java-version: '11'
- name: 'Build project'
run: |
- export NDK_VERSION=21d
- export ANDROID_NDK_PLATFORM=android-21
+ export QT_HOST_PATH="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64"
+ export NDK_VERSION=23c
+ export ANDROID_NDK_PLATFORM=android-23
export ANDROID_NDK_HOME=${{ runner.temp }}/android-ndk-r${NDK_VERSION}
export ANDROID_NDK_ROOT=$ANDROID_NDK_HOME
+ export ANDROID_CURRENT_ARCH=${{ matrix.arch }}
if [ ! -f $ANDROID_NDK_ROOT/ndk-build ]; then
- wget https://dl.google.com/android/repository/android-ndk-r${NDK_VERSION}-linux-x86_64.zip -qO ${{ runner.temp }}/ndk.zip &&
+ wget https://dl.google.com/android/repository/android-ndk-r${NDK_VERSION}-linux.zip -qO ${{ runner.temp }}/ndk.zip &&
unzip -q -d ${{ runner.temp }} ${{ runner.temp }}/ndk.zip ;
fi
diff --git a/.github/workflows/tag-deploy.yml b/.github/workflows/tag-deploy.yml
new file mode 100644
index 000000000..b88390f49
--- /dev/null
+++ b/.github/workflows/tag-deploy.yml
@@ -0,0 +1,142 @@
+name: 'Release deploy workflow'
+
+on:
+ workflow_dispatch:
+ # push:
+ # tags:
+ # - **
+
+jobs:
+
+ Build-Android-Release:
+ name: 'Build-Android-Release'
+ runs-on: ubuntu-latest
+
+ env:
+ QT_VERSION: 6.4.1
+ QIF_VERSION: 4.5
+
+ steps:
+ - name: 'Install desktop Qt'
+ uses: jurplel/install-qt-action@v3
+ with:
+ version: ${{ env.QT_VERSION }}
+ host: 'linux'
+ target: 'desktop'
+ arch: 'gcc_64'
+ modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
+ dir: ${{ runner.temp }}
+ setup-python: 'true'
+ set-env: 'true'
+ extra: '--external 7z'
+
+ - name: 'Install android Qt x86_64'
+ uses: jurplel/install-qt-action@v3
+ with:
+ version: ${{ env.QT_VERSION }}
+ host: 'linux'
+ target: 'android'
+ arch: 'android_x86_64'
+ modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
+ dir: ${{ runner.temp }}
+ setup-python: 'true'
+ set-env: 'true'
+ extra: '--external 7z'
+
+ - name: 'Install android Qt x86'
+ uses: jurplel/install-qt-action@v3
+ with:
+ version: ${{ env.QT_VERSION }}
+ host: 'linux'
+ target: 'android'
+ arch: 'android_x86'
+ modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
+ dir: ${{ runner.temp }}
+ setup-python: 'true'
+ set-env: 'true'
+ extra: '--external 7z'
+
+ - name: 'Install android Qt arm_v7'
+ uses: jurplel/install-qt-action@v3
+ with:
+ version: ${{ env.QT_VERSION }}
+ host: 'linux'
+ target: 'android'
+ arch: 'android_armv7'
+ modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
+ dir: ${{ runner.temp }}
+ setup-python: 'true'
+ set-env: 'true'
+ extra: '--external 7z'
+
+ - name: 'Install android Qt arm_v8'
+ uses: jurplel/install-qt-action@v3
+ with:
+ version: ${{ env.QT_VERSION }}
+ host: 'linux'
+ target: 'android'
+ arch: 'android_arm64_v8a'
+ modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
+ dir: ${{ runner.temp }}
+ setup-python: 'true'
+ set-env: 'true'
+ extra: '--external 7z'
+
+ - name: 'Get sources'
+ uses: actions/checkout@v3
+ with:
+ path: main
+ submodules: 'true'
+ fetch-depth: 10
+
+ - name: 'Preparations before keystore fetching'
+ run: |
+ mkdir keystore
+
+ - name: 'Getting keystore'
+ uses: actions/checkout@v3
+ with:
+ repository: amnezia-vpn/amnezia-android-certificates
+ ssh-key: ${{ secrets.ANDROID_CERTS_SSH_PRIVATE_KEY }}
+ path: keystore
+
+ - name: 'Setup ccache'
+ uses: hendrikmuhs/ccache-action@v1.2
+
+ - name: 'Setup Java'
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'temurin'
+ java-version: '11'
+
+ - name: 'Build project'
+ run: |
+ export QT_HOST_PATH="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64"
+ export NDK_VERSION=23c
+ export ANDROID_NDK_PLATFORM=android-23
+ export ANDROID_NDK_HOME=${{ runner.temp }}/android-ndk-r${NDK_VERSION}
+ export ANDROID_NDK_ROOT=$ANDROID_NDK_HOME
+
+ if [ ! -f $ANDROID_NDK_ROOT/ndk-build ]; then
+ wget https://dl.google.com/android/repository/android-ndk-r${NDK_VERSION}-linux.zip -qO ${{ runner.temp }}/ndk.zip &&
+ unzip -q -d ${{ runner.temp }} ${{ runner.temp }}/ndk.zip ;
+ fi
+
+ export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/android_arm64_v8a/bin
+ cd main
+ bash deploy/build_android.sh
+
+ - name: 'Signing APK'
+ run: |
+ pwd
+
+ ANDROID_BUILD_TOOLS_VERSION=30.0.3
+
+ ${ANDROID_HOME}/build-tools/${ANDROID_BUILD_TOOLS_VERSION}/zipalign -f -v 4 AmneziaVPN-release-unsigned.apk AmneziaVPN-release-aligned.apk
+ ${ANDROID_HOME}/build-tools/${ANDROID_BUILD_TOOLS_VERSION}/apksigner sign --out AmneziaVPN-release-signed.apk --ks keystore/debug.keystore --ks-key-alias ${{ secrets.DEBUG_ANDROID_KEYSTORE_KEY_ALIAS }} --ks-pass pass:${{secrets.DEBUG_ANDROID_KEYSTOTE_KEY_PASS }} AmneziaVPN-release-aligned.apk
+
+ - name: 'Upload'
+ uses: actions/upload-artifact@v3
+ with:
+ name: Release APK
+ path: ${{ runner.temp }}/main/AmneziaVPN-release-signed.apk
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6236396e2..fa8418198 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,6 +3,10 @@ cmake_minimum_required(VERSION 3.23.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT})
+if(ANDROID)
+ set(QT_ANDROID_BUILD_ALL_ABIS ON)
+endif()
+
add_subdirectory(client)
if(NOT IOS AND NOT ANDROID)
diff --git a/client/3rd/QtSsh/src/botan/botan.cmake b/client/3rd/QtSsh/src/botan/botan.cmake
index d363fe714..24a35d1c2 100644
--- a/client/3rd/QtSsh/src/botan/botan.cmake
+++ b/client/3rd/QtSsh/src/botan/botan.cmake
@@ -38,11 +38,13 @@ if(ANDROID)
# We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api
set(LIBS ${LIBS} Qt6::CorePrivate)
- set(ANDROID_ABIS ANDROID_TARGET_ARCH)
- link_directories(${CMAKE_CURRENT_LIST_DIR}/android/${ANDROID_TARGET_ARCH})
- set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/android/${ANDROID_TARGET_ARCH}/botan_all.h)
- set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/android/${ANDROID_TARGET_ARCH}/botan_all.cpp)
+ set(abi ${CMAKE_ANDROID_ARCH_ABI})
+
+ include_directories(${CMAKE_CURRENT_LIST_DIR}/android/${abi})
+ link_directories(${CMAKE_CURRENT_LIST_DIR}/android/${abi})
+ set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/android/${abi}/botan_all.h)
+ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/android/${abi}/botan_all.cpp)
endif()
if(IOS)
@@ -66,8 +68,4 @@ if(IOS)
include_directories(${CMAKE_CURRENT_LIST_DIR}/ios/iphone)
set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/ios/iphone/botan_all.h)
set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/ios/iphone/botan_all.cpp)
-
-
-
-
endif()
diff --git a/client/3rd/qtkeychain b/client/3rd/qtkeychain
index f197cdb93..c6f0b6631 160000
--- a/client/3rd/qtkeychain
+++ b/client/3rd/qtkeychain
@@ -1 +1 @@
-Subproject commit f197cdb935b0cfd9881fdc6860874cb8379d1238
+Subproject commit c6f0b66318f8da6917fb4681103f7303b1836194
diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
index 825ee3d08..004385b72 100644
--- a/client/CMakeLists.txt
+++ b/client/CMakeLists.txt
@@ -266,7 +266,6 @@ if(ANDROID)
# We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api
set(LIBS ${LIBS} Qt6::CorePrivate)
- set(ANDROID_ABIS ANDROID_TARGET_ARCH)
add_compile_definitions(MVPN_ANDROID)
@@ -275,12 +274,16 @@ if(ANDROID)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_controller.h
${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_notificationhandler.h
+ ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidutils.h
+ ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidvpnactivity.h
${CMAKE_CURRENT_LIST_DIR}/protocols/android_vpnprotocol.h
)
set(SOURCES ${SOURCES}
- ${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_controller.cp
+ ${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_controller.cpp
${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_notificationhandler.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidutils.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidvpnactivity.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/android_vpnprotocol.cpp
)
endif()
@@ -482,25 +485,26 @@ if(ANDROID)
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/NotificationUtil.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/Prefs.kt
- ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VpnLogger.kt
- ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VpnService.kt
- ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VpnServiceBinder.kt
+ ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VPNLogger.kt
+ ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VPNService.kt
+ ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VPNServiceBinder.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/AmneziaApp.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/PackageManagerHelper.java
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNActivity.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNApplication.java
+ ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNClientBinder.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNPermissionHelper.kt
${CMAKE_CURRENT_BINARY_DIR}
)
- set_property(TARGET ${PROJECT}
+ set_property(TARGET ${PROJECT} PROPERTY
QT_ANDROID_PACKAGE_SOURCE_DIR
${CMAKE_CURRENT_LIST_DIR}/android
)
- foreach(abi IN ANDROID_ABIS)
- if(ANDROID_TARGET_ARCH EQUAL ${abi})
- set(LIBS ${LIBS}
+ foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
+ if(CMAKE_ANDROID_ARCH_ABI STREQUAL ${abi})
+ set(LIBS ${LIBS}
${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/android/${abi}/libcrypto.a
${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/android/${abi}/libssl.a
)
@@ -510,7 +514,7 @@ if(ANDROID)
${CMAKE_CURRENT_LIST_DIR}/android/lib/wireguard/${abi}/libwg.so
${CMAKE_CURRENT_LIST_DIR}/android/lib/wireguard/${abi}/libwg-go.so
${CMAKE_CURRENT_LIST_DIR}/android/lib/wireguard/${abi}/libwg-quick.so
-
+
${CMAKE_CURRENT_LIST_DIR}/android/lib/openvpn/${abi}/libjbcrypto.so
${CMAKE_CURRENT_LIST_DIR}/android/lib/openvpn/${abi}/libopenvpn.so
${CMAKE_CURRENT_LIST_DIR}/android/lib/openvpn/${abi}/libopvpnutil.so
@@ -518,6 +522,7 @@ if(ANDROID)
${CMAKE_CURRENT_LIST_DIR}/android/lib/openvpn/${abi}/libovpnexec.so
)
endforeach()
+
endif()
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml
index 55cffe204..09a8775b6 100644
--- a/client/android/AndroidManifest.xml
+++ b/client/android/AndroidManifest.xml
@@ -21,13 +21,19 @@
Remove the comment if you do not require these default features. -->
-
+
+ android:exported="true">
+
+
-
+
-
-
-
+
+
+
-
+
@@ -58,14 +65,14 @@
-
+
-
-
-
+
+
+
-
+
@@ -73,14 +80,14 @@
-
+
-
-
-
+
+
+
-
+
@@ -88,102 +95,51 @@
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
+
diff --git a/client/android/build.gradle b/client/android/build.gradle
index 121297202..69eda3e58 100644
--- a/client/android/build.gradle
+++ b/client/android/build.gradle
@@ -20,7 +20,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.0.0'
+ classpath 'com.android.tools.build:gradle:7.2.1'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.21.0'
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.8.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
@@ -72,7 +72,11 @@ android {
compileSdkVersion androidCompileSdkVersion.toInteger()
- //buildToolsVersion '28.0.3'
+ buildToolsVersion androidBuildToolsVersion
+ ndkVersion androidNdkVersion
+
+ // Extract native libraries from the APK
+ packagingOptions.jniLibs.useLegacyPackaging true
dexOptions {
javaMaxHeapSize "3g"
@@ -81,9 +85,9 @@ android {
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
- java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
- aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
- res.srcDirs = [qt5AndroidDir + '/res', 'res']
+ java.srcDirs = [qtAndroidDir + '/src', 'src', 'java']
+ aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl']
+ res.srcDirs = [qtAndroidDir + '/res', 'res']
resources.srcDirs = ['resources']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
@@ -139,6 +143,7 @@ android {
debug {
//applicationIdSuffix ".debug"
//versionNameSuffix "-debug"
+ minifyEnabled false
externalNativeBuild {
cmake {
arguments "-DANDROID_PACKAGE_NAME=${groupName}", "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
diff --git a/client/android/gradle/wrapper/gradle-wrapper.properties b/client/android/gradle/wrapper/gradle-wrapper.properties
index 4e1cc9db6..669386b87 100644
--- a/client/android/gradle/wrapper/gradle-wrapper.properties
+++ b/client/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/client/android/res/values/libs.xml b/client/android/res/values/libs.xml
index 6b1a4a2a0..fe63866f7 100644
--- a/client/android/res/values/libs.xml
+++ b/client/android/res/values/libs.xml
@@ -1,11 +1,6 @@
-
- - https://download.qt.io/ministro/android/qt5/qt-5.14
-
-
-
+
@@ -19,4 +14,8 @@
+
+
+
+
diff --git a/client/android/src/org/amnezia/vpn/AuthHelper.java b/client/android/src/org/amnezia/vpn/AuthHelper.java
index b30aa8f50..940d03c27 100644
--- a/client/android/src/org/amnezia/vpn/AuthHelper.java
+++ b/client/android/src/org/amnezia/vpn/AuthHelper.java
@@ -3,7 +3,7 @@ package org.amnezia.vpn;
import android.content.Context;
import android.app.KeyguardManager;
import android.content.Intent;
-import org.qtproject.qt5.android.bindings.QtActivity;
+import org.qtproject.qt.android.bindings.QtActivity;
import static android.content.Context.KEYGUARD_SERVICE;
diff --git a/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt b/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt
index f40f95365..39e077dd1 100644
--- a/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt
+++ b/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt
@@ -31,6 +31,31 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna
private var mAlreadyInitialised = false
private var mService: VPNService = service
+ private var bytesInIndex = -1
+ private var bytesOutIndex = -1
+
+ init {
+ findConfigIndicies()
+ }
+
+ private fun findConfigIndicies() {
+ val n: Int = stats_n()
+
+ for (i in 0 until n) {
+ val name: String = stats_name(i)
+ if (name == "BYTES_IN") bytesInIndex = i
+ if (name == "BYTES_OUT") bytesOutIndex = i
+ }
+ }
+
+ fun getTotalRxBytes(): Long {
+ return stats_value(bytesInIndex)
+ }
+
+ fun getTotalTxBytes(): Long {
+ return stats_value(bytesOutIndex)
+ }
+
override fun run() {
val config: ClientAPI_Config = ClientAPI_Config()
diff --git a/client/android/src/org/amnezia/vpn/VPNService.kt b/client/android/src/org/amnezia/vpn/VPNService.kt
index 23e9ff0c5..6ca99e87d 100644
--- a/client/android/src/org/amnezia/vpn/VPNService.kt
+++ b/client/android/src/org/amnezia/vpn/VPNService.kt
@@ -41,6 +41,7 @@ import java.io.Closeable
import java.io.File
import java.io.FileDescriptor
import java.io.IOException
+import java.lang.Exception
import android.net.VpnService as BaseVpnService
@@ -153,31 +154,6 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
private var flags = 0
private var startId = 0
- private lateinit var mMessenger: Messenger
-
- internal class ExternalConfigImportHandler(
- context: Context,
- private val serviceBinder: VPNServiceBinder,
- private val applicationContext: Context = context.applicationContext
- ) : Handler() {
-
- override fun handleMessage(msg: Message) {
- when (msg.what) {
- IMPORT_COMMAND_CODE -> {
- val data = msg.data.getString(IMPORT_CONFIG_KEY)
-
- if (data != null) {
- serviceBinder.importConfig(data)
- }
- }
-
- else -> {
- super.handleMessage(msg)
- }
- }
- }
- }
-
fun init() {
if (mAlreadyInitialised) {
return
@@ -216,13 +192,6 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
override fun onBind(intent: Intent): IBinder {
Log.v(tag, "Aman: onBind....................")
- if (intent.action != null && intent.action == IMPORT_ACTION_CODE) {
- Log.v(tag, "Service bind for import of config")
- mMessenger = Messenger(ExternalConfigImportHandler(this, mBinder))
- return mMessenger.binder
- }
-
- Log.v(tag, "Regular service bind")
when (mProtocol) {
"shadowsocks" -> {
when (intent.action) {
@@ -306,9 +275,16 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
return mConnectionTime
}
- var isUp: Boolean
+ var isUp: Boolean = false
get() {
- return currentTunnelHandle >= 0
+ return when (mProtocol) {
+ "openvpn" -> {
+ field
+ }
+ else -> {
+ currentTunnelHandle >= 0
+ }
+ }
}
set(value) {
if (value) {
@@ -319,17 +295,52 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
mBinder.dispatchEvent(VPNServiceBinder.EVENTS.disconnected, "")
mConnectionTime = 0
}
+
val status: JSONObject
get() {
val deviceIpv4: String = ""
+
+ val status = when (mProtocol) {
+ "openvpn" -> {
+ if (mOpenVPNThreadv3 == null) {
+ Status(null, null, null, null)
+ } else {
+ val rx = mOpenVPNThreadv3?.getTotalRxBytes() ?: ""
+ val tx = mOpenVPNThreadv3?.getTotalTxBytes() ?: ""
+
+ Status(
+ rx.toString(),
+ tx.toString(),
+ if (mConfig!!.has("server")) { mConfig?.getJSONObject("server")?.getString("ipv4Gateway") } else {""},
+ if (mConfig!!.has("device")) { mConfig?.getJSONObject("device")?.getString("ipv4Address") } else {""}
+ )
+ }
+ }
+ else -> {
+ Status(
+ getConfigValue("rx_bytes"),
+ getConfigValue("tx_bytes"),
+ if (mConfig!!.has("server")) { mConfig?.getJSONObject("server")?.getString("ipv4Gateway") } else {""},
+ if (mConfig!!.has("server")) {mConfig?.getJSONObject("device")?.getString("ipv4Address") } else {""}
+ )
+ }
+ }
+
return JSONObject().apply {
- putOpt("rx_bytes", getConfigValue("rx_bytes"))
- putOpt("tx_bytes", getConfigValue("tx_bytes"))
- putOpt("endpoint", mConfig?.getJSONObject("server")?.getString("ipv4Gateway"))
- putOpt("deviceIpv4", mConfig?.getJSONObject("device")?.getString("ipv4Address"))
+ putOpt("rx_bytes", status.rxBytes)
+ putOpt("tx_bytes", status.txBytes)
+ putOpt("endpoint", status.endpoint)
+ putOpt("deviceIpv4", status.device)
}
}
+ data class Status(
+ var rxBytes: String?,
+ var txBytes: String?,
+ var endpoint: String?,
+ var device: String?
+ )
+
/*
* Checks if the VPN Permission is given.
* If the permission is given, returns true
@@ -677,6 +688,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
private fun startOpenVpn() {
mOpenVPNThreadv3 = OpenVPNThreadv3(this)
+
Thread({
mOpenVPNThreadv3?.run()
}).start()
diff --git a/client/android/src/org/amnezia/vpn/VPNServiceBinder.kt b/client/android/src/org/amnezia/vpn/VPNServiceBinder.kt
index 640a0657f..239269a5b 100644
--- a/client/android/src/org/amnezia/vpn/VPNServiceBinder.kt
+++ b/client/android/src/org/amnezia/vpn/VPNServiceBinder.kt
@@ -33,6 +33,7 @@ class VPNServiceBinder(service: VPNService) : Binder() {
const val setNotificationText = 8
const val setFallBackNotification = 9
const val shareConfig = 10
+ const val importConfig = 11
}
/**
@@ -75,13 +76,14 @@ class VPNServiceBinder(service: VPNService) : Binder() {
ACTIONS.resumeActivate -> {
// [data] is empty
// Activate the current tunnel
- try {
- mResumeConfig?.let { this.mService.turnOn(it) }
+ Log.i(tag, "resume activate")
+ try {
+ mResumeConfig?.let { this.mService.turnOn(it) }
} catch (e: Exception) {
Log.e(tag, "An Error occurred while enabling the VPN: ${e.localizedMessage}")
}
return true
- }
+ }
ACTIONS.deactivate -> {
// [data] here is empty
@@ -90,6 +92,7 @@ class VPNServiceBinder(service: VPNService) : Binder() {
}
ACTIONS.registerEventListener -> {
+ Log.i(tag, "register: start")
// [data] contains the Binder that we need to dispatch the Events
val binder = data.readStrongBinder()
mListener = binder
@@ -150,6 +153,23 @@ class VPNServiceBinder(service: VPNService) : Binder() {
return true
}
+ ACTIONS.importConfig -> {
+ val buffer = data.readString()
+
+ val obj = JSONObject()
+ obj.put("config", buffer)
+
+ val resultString = obj.toString()
+
+ Log.i(tag, "Transact import config request")
+
+ if (mListener != null) {
+ dispatchEvent(EVENTS.configImport, resultString)
+ } else {
+ mImportedConfig = resultString
+ }
+ }
+
IBinder.LAST_CALL_TRANSACTION -> {
Log.e(tag, "The OS Requested to shut down the VPN")
this.mService.turnOff()
@@ -176,9 +196,12 @@ class VPNServiceBinder(service: VPNService) : Binder() {
try {
mListener?.let {
if (it.isBinderAlive) {
+ Log.i(tag, "Dispatching event: binder alive")
val data = Parcel.obtain()
data.writeByteArray(payload?.toByteArray(charset("UTF-8")))
it.transact(code, data, Parcel.obtain(), 0)
+ } else {
+ Log.i(tag, "Dispatching event: binder NOT alive")
}
}
} catch (e: DeadObjectException) {
@@ -197,23 +220,7 @@ class VPNServiceBinder(service: VPNService) : Binder() {
const val statisticUpdate = 3
const val backendLogs = 4
const val activationError = 5
- const val configImport = 6
- }
-
- fun importConfig(config: String) {
- val obj = JSONObject()
- obj.put("config", config)
-
- val resultString = obj.toString()
-
- Log.i(tag, "Transact import config request")
-
- if (mListener != null) {
- Log.i(tag, "binder alive")
- dispatchEvent(EVENTS.configImport, resultString)
- } else {
- Log.i(tag, "binder NOT alive")
- mImportedConfig = resultString
- }
+ const val permissionRequired = 6
+ const val configImport = 7
}
}
diff --git a/client/android/src/org/amnezia/vpn/qt/AmneziaApp.kt b/client/android/src/org/amnezia/vpn/qt/AmneziaApp.kt
index 7888f6d6d..24c33ffc3 100644
--- a/client/android/src/org/amnezia/vpn/qt/AmneziaApp.kt
+++ b/client/android/src/org/amnezia/vpn/qt/AmneziaApp.kt
@@ -3,8 +3,8 @@ package org.amnezia.vpn.qt
import android.content.res.Configuration
import org.amnezia.vpn.shadowsocks.core.Core
import org.amnezia.vpn.shadowsocks.core.VpnManager
-import org.qtproject.qt5.android.bindings.QtActivity
-import org.qtproject.qt5.android.bindings.QtApplication
+import org.qtproject.qt.android.bindings.QtActivity
+import org.qtproject.qt.android.bindings.QtApplication
import android.app.Application
class AmneziaApp: Application() {
diff --git a/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt b/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt
index c5b5107e1..673c4b593 100644
--- a/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt
+++ b/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt
@@ -1,3 +1,7 @@
+/* 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.qt;
import android.Manifest
@@ -20,18 +24,33 @@ 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.qt5.android.bindings.QtActivity
+import org.qtproject.qt.android.bindings.QtActivity
import java.io.*
-class VPNActivity : org.qtproject.qt5.android.bindings.QtActivity() {
+class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
private var configString: String? = null
- private var vpnServiceBinder: Messenger? = null
+ private var vpnServiceBinder: IBinder? = null
private var isBound = false
private val TAG = "VPNActivity"
private val STORAGE_PERMISSION_CODE = 42
+ companion object {
+ private lateinit var instance: VPNActivity
+
+ @JvmStatic fun getInstance(): VPNActivity {
+ return instance
+ }
+
+ @JvmStatic fun connectService() {
+ VPNActivity.getInstance().initServiceConnection()
+ }
+
+ @JvmStatic fun sendToService(actionCode: Int, body: String) {
+ VPNActivity.getInstance().dispatchParcel(actionCode, body)
+ }
+ }
override fun onCreate(savedInstanceState: Bundle?) {
val newIntent = intent
@@ -42,6 +61,48 @@ class VPNActivity : org.qtproject.qt5.android.bindings.QtActivity() {
}
super.onCreate(savedInstanceState)
+
+ instance = this;
+ }
+
+ override fun getSystemService(name: String): Any? {
+ return if (Build.VERSION.SDK_INT >= 29 && name == "clipboard") {
+ // QT will always attempt to read the clipboard if content is there.
+ // since we have no use of the clipboard in android 10+
+ // we _can_ return null
+ // And we defnitly should since android 12 displays clipboard access.
+ null
+ } else {
+ super.getSystemService(name)
+ }
+ }
+
+ external fun handleBackButton(): Boolean
+
+ external fun onServiceMessage(actionCode: Int, body: String?)
+ external fun qtOnServiceConnected()
+ external fun qtOnServiceDisconnected()
+
+ private fun dispatchParcel(actionCode: Int, body: String) {
+ if (!isBound) {
+ Log.d(TAG, "dispatchParcel: not bound")
+ return
+ } else {
+ Log.d(TAG, "dispatchParcel: bound")
+ }
+
+ 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) {
@@ -63,19 +124,11 @@ class VPNActivity : org.qtproject.qt5.android.bindings.QtActivity() {
override fun onResume() {
super.onResume()
- if (configString != null && !isBound) {
- bindVpnService()
+ if (configString != null && isBound) {
+ sendImportConfigCommand()
}
}
- override fun onPause() {
- if (vpnServiceBinder != null && isBound) {
- unbindService(connection)
- isBound = false
- }
- super.onPause()
- }
-
private fun isReadStorageAllowed(): Boolean {
val permissionStatus = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
return permissionStatus == PackageManager.PERMISSION_GRANTED
@@ -90,8 +143,15 @@ class VPNActivity : org.qtproject.qt5.android.bindings.QtActivity() {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Storage read permission granted")
+ if (configString == null) {
+ configString = processIntent(intent, intent.action!!)
+ }
+
if (configString != null) {
- bindVpnService()
+ Log.d(TAG, "not empty")
+ sendImportConfigCommand()
+ } else {
+ Log.d(TAG, "empty")
}
} else {
Toast.makeText(this, "Oops you just denied the permission", Toast.LENGTH_LONG).show()
@@ -99,17 +159,6 @@ class VPNActivity : org.qtproject.qt5.android.bindings.QtActivity() {
}
}
- private fun bindVpnService() {
- try {
- val intent = Intent(this, VPNService::class.java)
- intent.action = IMPORT_ACTION_CODE
-
- bindService(intent, connection, Context.BIND_AUTO_CREATE)
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }
-
private fun processIntent(intent: Intent, action: String): String? {
val scheme = intent.scheme
@@ -158,23 +207,35 @@ class VPNActivity : org.qtproject.qt5.android.bindings.QtActivity() {
}
}
+ 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 var connection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, binder: IBinder) {
- vpnServiceBinder = Messenger(binder)
+ vpnServiceBinder = binder
- if (configString != null) {
- val msg: Message = Message.obtain(null, IMPORT_COMMAND_CODE, 0, 0)
- val bundle = Bundle()
- bundle.putString(IMPORT_CONFIG_KEY, configString!!)
- msg.data = bundle
-
- try {
- vpnServiceBinder?.send(msg)
- } catch (e: RemoteException) {
- e.printStackTrace()
- }
-
- configString = null
+ // 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
@@ -183,9 +244,75 @@ class VPNActivity : org.qtproject.qt5.android.bindings.QtActivity() {
override fun onServiceDisconnected(className: ComponentName) {
vpnServiceBinder = null
isBound = false
+ qtOnServiceDisconnected();
}
}
+ 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, VPNService::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_REQURED = 6
+ private val EVENT_DISCONNECTED = 2
+
+
+ fun onPermissionRequest(code: Int, data: Parcel?) {
+ if (code != EVENT_PERMISSION_REQURED) {
+ 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)
+ }
+
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK && event.repeatCount == 0) {
onBackPressed()
diff --git a/client/android/src/org/amnezia/vpn/qt/VPNApplication.java b/client/android/src/org/amnezia/vpn/qt/VPNApplication.java
index 295146339..639b5a1e9 100644
--- a/client/android/src/org/amnezia/vpn/qt/VPNApplication.java
+++ b/client/android/src/org/amnezia/vpn/qt/VPNApplication.java
@@ -5,7 +5,7 @@ import androidx.annotation.NonNull;
import org.amnezia.vpn.shadowsocks.core.Core;
import org.amnezia.vpn.shadowsocks.core.VpnManager;
-public class VPNApplication extends org.qtproject.qt5.android.bindings.QtApplication {
+public class VPNApplication extends org.qtproject.qt.android.bindings.QtApplication {
private static VPNApplication instance;
@Override
diff --git a/client/android/src/org/amnezia/vpn/qt/VPNClientBinder.kt b/client/android/src/org/amnezia/vpn/qt/VPNClientBinder.kt
new file mode 100644
index 000000000..6a8fa086d
--- /dev/null
+++ b/client/android/src/org/amnezia/vpn/qt/VPNClientBinder.kt
@@ -0,0 +1,27 @@
+/* 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.qt
+
+import android.os.Binder
+import android.os.Parcel
+import android.util.Log
+
+const val permissionRequired = 6
+
+class VPNClientBinder() : Binder() {
+
+ override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
+ if (code == permissionRequired) {
+ VPNActivity.getInstance().onPermissionRequest(code, data)
+ return true
+ }
+
+ val buffer = data.createByteArray()
+ val stringData = buffer?.let { String(it) }
+ VPNActivity.getInstance().onServiceMessage(code, stringData)
+
+ return true
+ }
+}
diff --git a/client/android/src/org/ftylitak/qzxing/QZXingLiveActivity.java b/client/android/src/org/ftylitak/qzxing/QZXingLiveActivity.java
index 37ccb6ce3..b6a4649d3 100644
--- a/client/android/src/org/ftylitak/qzxing/QZXingLiveActivity.java
+++ b/client/android/src/org/ftylitak/qzxing/QZXingLiveActivity.java
@@ -2,7 +2,7 @@ package org.ftylitak.qzxing;
import android.Manifest;
import android.content.pm.PackageManager;
-import org.qtproject.qt5.android.bindings.QtActivity;
+import org.qtproject.qt.android.bindings.QtActivity;
import static org.ftylitak.qzxing.Utilities.REQUEST_CAMERA;
public class QZXingLiveActivity extends QtActivity {
diff --git a/client/client.pro b/client/client.pro
index 785005063..4810d0bab 100644
--- a/client/client.pro
+++ b/client/client.pro
@@ -1,4 +1,4 @@
-QT += widgets core gui network xml remoteobjects quick svg quickcontrols2 core5compat
+QT += widgets core gui network xml remoteobjects quick svg quickcontrols2
equals(QT_MAJOR_VERSION, 6): QT += core5compat
TARGET = AmneziaVPN
@@ -241,14 +241,8 @@ android {
versionAtLeast(QT_VERSION, 6.0.0) {
# We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api
- QT+=core-private
- ANDROID_ABIS=ANDROID_TARGET_ARCH
-
- # for not changing qtkeychain sources for qt6
- QT -= androidextras
- }
- else {
- QT += androidextras
+ QT += core-private
+ ANDROID_ABIS = $$ANDROID_TARGET_ARCH
}
DEFINES += MVPN_ANDROID
@@ -258,13 +252,16 @@ android {
HEADERS += \
platforms/android/android_controller.h \
platforms/android/android_notificationhandler.h \
- protocols/android_vpnprotocol.h
+ protocols/android_vpnprotocol.h \
+ platforms/android/androidutils.h \
+ platforms/android/androidvpnactivity.h
SOURCES += \
platforms/android/android_controller.cpp \
platforms/android/android_notificationhandler.cpp \
- protocols/android_vpnprotocol.cpp
-
+ protocols/android_vpnprotocol.cpp \
+ platforms/android/androidutils.cpp \
+ platforms/android/androidvpnactivity.cpp
DISTFILES += \
android/AndroidManifest.xml \
@@ -293,6 +290,7 @@ android {
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
for (abi, ANDROID_ABIS): {
+
equals(ANDROID_TARGET_ARCH,$$abi) {
LIBS += $$PWD/3rd/OpenSSL/lib/android/$${abi}/libcrypto.a
LIBS += $$PWD/3rd/OpenSSL/lib/android/$${abi}/libssl.a
diff --git a/client/cmake/3rdparty.cmake b/client/cmake/3rdparty.cmake
index 2fcfa9d1f..a87b4df57 100644
--- a/client/cmake/3rdparty.cmake
+++ b/client/cmake/3rdparty.cmake
@@ -19,8 +19,8 @@ add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
set(LIBS ${LIBS} qt6keychain)
include_directories(
- ${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/include
${CLIENT_ROOT_DIR}/3rd/OpenSSL/include
+ ${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/include
${CLIENT_ROOT_DIR}/3rd/qtkeychain
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
)
diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp
index 12589664b..b25fce78f 100644
--- a/client/platforms/android/android_controller.cpp
+++ b/client/platforms/android/android_controller.cpp
@@ -1,3 +1,7 @@
+/* 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
#include
#include
@@ -7,51 +11,131 @@
#include
#include
-#include
-
#include "android_controller.h"
-#include "core/errorstrings.h"
+#include "private/qandroidextras_p.h"
#include "ui/pages_logic/StartPageLogic.h"
-// Binder Codes for VPNServiceBinder
-// See also - VPNServiceBinder.kt
-// Actions that are Requestable
-const int ACTION_ACTIVATE = 1;
-const int ACTION_DEACTIVATE = 2;
-const int ACTION_REGISTER_LISTENER = 3;
-const int ACTION_REQUEST_STATISTIC = 4;
-const int ACTION_REQUEST_GET_LOG = 5;
-const int ACTION_REQUEST_CLEANUP_LOG = 6;
-const int ACTION_RESUME_ACTIVATE = 7;
-const int ACTION_SET_NOTIFICATION_TEXT = 8;
-const int ACTION_SET_NOTIFICATION_FALLBACK = 9;
-const int ACTION_SHARE_CONFIG = 10;
-
-// Event Types that will be Dispatched after registration
-const int EVENT_INIT = 0;
-const int EVENT_CONNECTED = 1;
-const int EVENT_DISCONNECTED = 2;
-const int EVENT_STATISTIC_UPDATE = 3;
-const int EVENT_BACKEND_LOGS = 4;
-const int EVENT_ACTIVATION_ERROR = 5;
-const int EVENT_CONFIG_IMPORT = 6;
+#include "androidvpnactivity.h"
+#include "androidutils.h"
namespace {
AndroidController* s_instance = nullptr;
constexpr auto PERMISSIONHELPER_CLASS =
"org/amnezia/vpn/qt/VPNPermissionHelper";
-
} // namespace
-AndroidController::AndroidController():
- m_binder(this)
+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();
+
+ 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";
+
+ isConnected = true;
+
+ emit scheduleStatusCheckSignal();
+
+ emit connectionStateChanged(VpnProtocol::Connected);
+ }, Qt::QueuedConnection);
+
+ connect(activity, &AndroidVPNActivity::eventDisconnected, this,
+ [this]() {
+ qDebug() << "Transact: disconnected";
+
+ isConnected = false;
+
+ emit connectionStateChanged(VpnProtocol::Disconnected);
+ }, Qt::QueuedConnection);
+
+ connect(activity, &AndroidVPNActivity::eventStatisticUpdate, this,
+ [this](const QString& parcelBody) {
+ qDebug() << "Transact: update";
+
+ 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);
+
+ connect(activity, &AndroidVPNActivity::eventActivationError, this,
+ [this](const QString& parcelBody) {
+ Q_UNUSED(parcelBody)
+ qDebug() << "Transact: error";
+ emit connectionStateChanged(VpnProtocol::Error);
+ }, 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;
+ importConfig(buffer);
+ }, Qt::QueuedConnection);
+
+ connect(activity, &AndroidVPNActivity::serviceDisconnected, this,
+ [this]() {
+ qDebug() << "Transact: service disconnected";
+ m_serviceConnected = false;
+ }, Qt::QueuedConnection);
}
AndroidController* AndroidController::instance() {
- if (!s_instance) s_instance = new AndroidController();
+ if (!s_instance) {
+ s_instance = new AndroidController();
+ }
+
return s_instance;
}
@@ -65,71 +149,43 @@ bool AndroidController::initialize(StartPageLogic *startPageLogic)
JNINativeMethod methods[]{{"startActivityForResult",
"(Landroid/content/Intent;)V",
reinterpret_cast(startActivityForResult)}};
- QAndroidJniObject javaClass(PERMISSIONHELPER_CLASS);
- QAndroidJniEnvironment env;
+ QJniObject javaClass(PERMISSIONHELPER_CLASS);
+ QJniEnvironment env;
jclass objectClass = env->GetObjectClass(javaClass.object());
- env->RegisterNatives(objectClass, methods,
- sizeof(methods) / sizeof(methods[0]));
+ env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0]));
env->DeleteLocalRef(objectClass);
- auto appContext = QtAndroid::androidActivity().callObjectMethod(
- "getApplicationContext", "()Landroid/content/Context;");
+ AndroidVPNActivity::connectService();
- QAndroidJniObject::callStaticMethod(
- "org/amnezia/vpn/VPNService", "startService",
- "(Landroid/content/Context;)V", appContext.object());
-
- // Start the VPN Service (if not yet) and Bind to it
- const bool bindResult = QtAndroid::bindService(
- QAndroidIntent(appContext.object(), "org.amnezia.vpn.VPNService"),
- *this, QtAndroid::BindFlag::AutoCreate);
- qDebug() << "Binding to the service..." << bindResult;
-
- return bindResult;
+ return true;
}
ErrorCode AndroidController::start()
{
-
- //qDebug().noquote() << "AndroidController::start" << QJsonDocument(m_rawConfig).toJson();
qDebug() << "Prompting for VPN permission";
- auto appContext = QtAndroid::androidActivity().callObjectMethod(
+ QJniObject activity = AndroidUtils::getActivity();
+ auto appContext = activity.callObjectMethod(
"getApplicationContext", "()Landroid/content/Context;");
- QAndroidJniObject::callStaticMethod(
+ QJniObject::callStaticMethod(
PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V",
appContext.object());
- QAndroidParcel sendData;
- sendData.writeData(QJsonDocument(m_vpnConfig).toJson());
- bool activateResult = false;
- while (!activateResult){
- activateResult = m_serviceBinder.transact(ACTION_ACTIVATE, sendData, nullptr);
- }
+ QJsonDocument doc(m_vpnConfig);
+ AndroidVPNActivity::sendToService(ServiceAction::ACTION_ACTIVATE, doc.toJson());
- return activateResult ? NoError : UnknownError;
+ return NoError;
}
void AndroidController::stop() {
qDebug() << "AndroidController::stop";
-// if (reason != ReasonNone) {
-// // Just show that we're disconnected
-// // we're doing the actual disconnect once
-// // the vpn-service has the new server ready in Action->Activate
-// emit disconnected();
-// qCritical() << "deactivation skipped for Switching";
-// return;
-// }
-
- QAndroidParcel nullData;
- m_serviceBinder.transact(ACTION_DEACTIVATE, nullData, nullptr);
+ AndroidVPNActivity::sendToService(ServiceAction::ACTION_DEACTIVATE, QString());
}
// Activates the tunnel that is currently set
// in the VPN Service
void AndroidController::resumeStart() {
- QAndroidParcel nullData;
- m_serviceBinder.transact(ACTION_RESUME_ACTIVATE, nullData, nullptr);
+ AndroidVPNActivity::sendToService(ServiceAction::ACTION_RESUME_ACTIVATE, QString());
}
/*
@@ -138,14 +194,13 @@ void AndroidController::resumeStart() {
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);
- QAndroidParcel data;
- data.writeData(doc.toJson());
- m_serviceBinder.transact(ACTION_SET_NOTIFICATION_TEXT, data, nullptr);
+ 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) {
@@ -153,9 +208,8 @@ void AndroidController::shareConfig(const QString& configContent, const QString&
rootObject["data"] = configContent;
rootObject["suggestedName"] = suggestedName;
QJsonDocument doc(rootObject);
- QAndroidParcel parcel;
- parcel.writeData(doc.toJson());
- m_serviceBinder.transact(ACTION_SHARE_CONFIG, parcel, nullptr);
+
+ AndroidVPNActivity::sendToService(ServiceAction::ACTION_SHARE_CONFIG, doc.toJson());
}
/*
@@ -164,64 +218,40 @@ void AndroidController::shareConfig(const QString& configContent, const QString&
* 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);
- QAndroidParcel data;
- data.writeData(doc.toJson());
- m_serviceBinder.transact(ACTION_SET_NOTIFICATION_FALLBACK, data, nullptr);
+ 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() {
- qDebug() << "check status";
+ qDebug() << "check status";
- QAndroidParcel nullParcel;
- m_serviceBinder.transact(ACTION_REQUEST_STATISTIC, nullParcel, nullptr);
+ AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, QString());
}
void AndroidController::getBackendLogs(std::function&& a_callback) {
- qDebug() << "get logs";
+ qDebug() << "get logs";
- m_logCallback = std::move(a_callback);
- QAndroidParcel nullData, replyData;
- m_serviceBinder.transact(ACTION_REQUEST_GET_LOG, nullData, &replyData);
+ m_logCallback = std::move(a_callback);
+
+ AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_GET_LOG, QString());
}
void AndroidController::cleanupBackendLogs() {
- qDebug() << "cleanup logs";
+ qDebug() << "cleanup logs";
- QAndroidParcel nullParcel;
- m_serviceBinder.transact(ACTION_REQUEST_CLEANUP_LOG, nullParcel, nullptr);
+ AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_CLEANUP_LOG, QString());
}
void AndroidController::importConfig(const QString& data){
m_startPageLogic->importConnectionFromCode(data);
}
-void AndroidController::onServiceConnected(
- const QString& name, const QAndroidBinder& serviceBinder) {
- qDebug() << "Server " + name + " connected";
-
- Q_UNUSED(name);
-
- m_serviceBinder = serviceBinder;
-
- // Send the Service our Binder to recive incoming Events
- QAndroidParcel binderParcel;
- binderParcel.writeBinder(m_binder);
- m_serviceBinder.transact(ACTION_REGISTER_LISTENER, binderParcel, nullptr);
-}
-
-void AndroidController::onServiceDisconnected(const QString& name) {
- qDebug() << "Server disconnected";
- m_serviceConnected = false;
- Q_UNUSED(name);
- // TODO: Maybe restart? Or crash?
-}
-
const QJsonObject &AndroidController::vpnConfig() const
{
return m_vpnConfig;
@@ -232,86 +262,14 @@ void AndroidController::setVpnConfig(const QJsonObject &newVpnConfig)
m_vpnConfig = newVpnConfig;
}
-
-/**
- * @brief AndroidController::VPNBinder::onTransact
- * @param code the Event-Type we get From the VPNService See
- * @param data - Might contain UTF-8 JSON in case the Event has a payload
- * @param reply - always null
- * @param flags - unused
- * @return Returns true is the code was a valid Event Code
- */
-bool AndroidController::VPNBinder::onTransact(int code,
- const QAndroidParcel& data,
- const QAndroidParcel& reply,
- QAndroidBinder::CallType flags) {
- Q_UNUSED(data);
- Q_UNUSED(reply);
- Q_UNUSED(flags);
-
- QJsonDocument doc;
- QString buffer;
- switch (code) {
- case EVENT_INIT:
- qDebug() << "Transact: init";
- doc = QJsonDocument::fromJson(data.readData());
- emit m_controller->initialized(
- true, doc.object()["connected"].toBool(),
- QDateTime::fromMSecsSinceEpoch(
- doc.object()["time"].toVariant().toLongLong()));
- // Pass a localised version of the Fallback string for the Notification
- m_controller->setFallbackConnectedNotification();
-
- break;
- case EVENT_CONNECTED:
- qDebug() << "Transact: connected";
- emit m_controller->connectionStateChanged(VpnProtocol::Connected);
- break;
- case EVENT_DISCONNECTED:
- qDebug() << "Transact: disconnected";
- emit m_controller->connectionStateChanged(VpnProtocol::Disconnected);
- break;
- case EVENT_STATISTIC_UPDATE:
- qDebug() << "Transact:: update";
-
- // Data is here a JSON String
- doc = QJsonDocument::fromJson(data.readData());
- // TODO update counters
-// emit m_controller->statusUpdated(doc.object()["endpoint"].toString(),
-// doc.object()["deviceIpv4"].toString(),
-// doc.object()["totalTX"].toInt(),
-// doc.object()["totalRX"].toInt());
- break;
- case EVENT_BACKEND_LOGS:
- qDebug() << "Transact: backend logs";
-
- buffer = readUTF8Parcel(data);
- if (m_controller->m_logCallback) {
- m_controller->m_logCallback(buffer);
- }
- break;
- case EVENT_ACTIVATION_ERROR:
- qDebug() << "Transact: error";
- emit m_controller->connectionStateChanged(VpnProtocol::Error);
- break;
- case EVENT_CONFIG_IMPORT:
- qDebug() << "Transact: config import";
- doc = QJsonDocument::fromJson(data.readData());
- buffer = doc.object()["config"].toString();
- qDebug() << "Transact: config string" << buffer;
- m_controller->importConfig(buffer);
- break;
- default:
- qWarning() << "Transact: Invalid!";
- break;
- }
-
- return true;
-}
-
-QString AndroidController::VPNBinder::readUTF8Parcel(QAndroidParcel data) {
- // 106 is the Code for UTF-8
- return QTextCodec::codecForMib(106)->toUnicode(data.readData());
+void AndroidController::scheduleStatusCheckSlot()
+{
+ QTimer::singleShot(1000, [this]() {
+ if (isConnected) {
+ checkStatus();
+ emit scheduleStatusCheckSignal();
+ }
+ });
}
const int ACTIVITY_RESULT_OK = 0xffffffff;
@@ -322,34 +280,34 @@ const int ACTIVITY_RESULT_OK = 0xffffffff;
*/
void AndroidController::startActivityForResult(JNIEnv *env, jobject, jobject intent)
{
- qDebug() << "start activity";
+ qDebug() << "start vpnPermissionHelper";
Q_UNUSED(env);
- QtAndroid::startActivity(intent, 1337,
- [](int receiverRequestCode, int resultCode,
- const QAndroidJniObject& data) {
- // Currently this function just used in
- // VPNService.kt::checkPersmissions. 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);
- AndroidController* controller =
- AndroidController::instance();
- if (!controller) {
- return;
- }
+ QtAndroidPrivate::startActivity(intent, 1337,
+ [](int receiverRequestCode, int resultCode,
+ const QJniObject& data) {
+ // Currently this function just used in
+ // VPNService.kt::checkPersmissions. 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);
- 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(VpnProtocol::Disconnected);
- });
+ 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(VpnProtocol::Disconnected);
+ });
return;
}
diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h
index b2543d5dd..ddaa0d79f 100644
--- a/client/platforms/android/android_controller.h
+++ b/client/platforms/android/android_controller.h
@@ -1,16 +1,21 @@
+/* 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
+#include
+#include
-#include "ui/uilogic.h"
#include "ui/pages_logic/StartPageLogic.h"
#include "protocols/vpnprotocol.h"
+
using namespace amnezia;
-class AndroidController : public QObject, public QAndroidServiceConnection
+class AndroidController : public QObject
{
Q_OBJECT
@@ -34,10 +39,6 @@ public:
void cleanupBackendLogs();
void importConfig(const QString& data);
- // from QAndroidServiceConnection
- void onServiceConnected(const QString& name, const QAndroidBinder& serviceBinder) override;
- void onServiceDisconnected(const QString& name) override;
-
const QJsonObject &vpnConfig() const;
void setVpnConfig(const QJsonObject &newVpnConfig);
@@ -52,13 +53,18 @@ signals:
void initialized(bool status, bool connected,
const QDateTime& connectionDate);
+ void statusUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4);
+ void scheduleStatusCheckSignal();
+
protected slots:
+ void scheduleStatusCheckSlot();
protected:
private:
- //Protocol m_protocol;
+ bool m_init = false;
+
QJsonObject m_vpnConfig;
StartPageLogic *m_startPageLogic;
@@ -66,24 +72,11 @@ private:
bool m_serviceConnected = false;
std::function m_logCallback;
- QAndroidBinder m_serviceBinder;
- class VPNBinder : public QAndroidBinder {
- public:
- VPNBinder(AndroidController* controller) : m_controller(controller) {}
-
- bool onTransact(int code, const QAndroidParcel& data,
- const QAndroidParcel& reply,
- QAndroidBinder::CallType flags) override;
-
- QString readUTF8Parcel(QAndroidParcel data);
-
- private:
- AndroidController* m_controller = nullptr;
- };
-
- VPNBinder m_binder;
-
static void startActivityForResult(JNIEnv* env, jobject /*thiz*/, jobject intent);
+
+ bool isConnected = false;
+
+ void scheduleStatusCheck();
};
#endif // ANDROID_CONTROLLER_H
diff --git a/client/platforms/android/androidutils.cpp b/client/platforms/android/androidutils.cpp
new file mode 100644
index 000000000..5e9f094cf
--- /dev/null
+++ b/client/platforms/android/androidutils.cpp
@@ -0,0 +1,174 @@
+/* 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 "androidutils.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "jni.h"
+
+namespace {
+ AndroidUtils* s_instance = nullptr;
+} // namespace
+
+// static
+QString AndroidUtils::GetDeviceName() {
+ QJniEnvironment env;
+ jclass BUILD = env->FindClass("android/os/Build");
+ jfieldID model = env->GetStaticFieldID(BUILD, "MODEL", "Ljava/lang/String;");
+ jstring value = (jstring)env->GetStaticObjectField(BUILD, model);
+
+ if (!value) {
+ return QString("Android Device");
+ }
+
+ const char* buffer = env->GetStringUTFChars(value, nullptr);
+ if (!buffer) {
+ return QString("Android Device");
+ }
+
+ QString res(buffer);
+ env->ReleaseStringUTFChars(value, buffer);
+
+ return res;
+};
+
+// static
+AndroidUtils* AndroidUtils::instance() {
+ if (!s_instance) {
+ Q_ASSERT(qApp);
+ s_instance = new AndroidUtils(qApp);
+ }
+
+ return s_instance;
+}
+
+AndroidUtils::AndroidUtils(QObject* parent) : QObject(parent) {
+ Q_ASSERT(!s_instance);
+ s_instance = this;
+}
+
+AndroidUtils::~AndroidUtils() {
+ Q_ASSERT(s_instance == this);
+ s_instance = nullptr;
+}
+
+// static
+void AndroidUtils::dispatchToMainThread(std::function callback) {
+ QTimer* timer = new QTimer();
+ timer->moveToThread(qApp->thread());
+ timer->setSingleShot(true);
+ QObject::connect(timer, &QTimer::timeout, [=]() {
+ callback();
+ timer->deleteLater();
+ });
+ QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection);
+}
+
+// static
+QByteArray AndroidUtils::getQByteArrayFromJString(JNIEnv* env, jstring data) {
+ const char* buffer = env->GetStringUTFChars(data, nullptr);
+ if (!buffer) {
+ qDebug() << "getQByteArrayFromJString - failed to parse data.";
+ return QByteArray();
+ }
+
+ QByteArray out(buffer);
+ env->ReleaseStringUTFChars(data, buffer);
+ return out;
+}
+
+// static
+QString AndroidUtils::getQStringFromJString(JNIEnv* env, jstring data) {
+ const char* buffer = env->GetStringUTFChars(data, nullptr);
+ if (!buffer) {
+ qDebug() << "getQStringFromJString - failed to parse data.";
+ return QString();
+ }
+
+ QString out(buffer);
+ env->ReleaseStringUTFChars(data, buffer);
+ return out;
+}
+
+// static
+QJsonObject AndroidUtils::getQJsonObjectFromJString(JNIEnv* env, jstring data) {
+ QByteArray raw(getQByteArrayFromJString(env, data));
+ QJsonParseError jsonError;
+ QJsonDocument json = QJsonDocument::fromJson(raw, &jsonError);
+ if (QJsonParseError::NoError != jsonError.error) {
+ qDebug() << "getQJsonObjectFromJstring - error parsing json. Code: "
+ << jsonError.error << "Offset: " << jsonError.offset
+ << "Message: " << jsonError.errorString()
+ << "Data: " << raw;
+ return QJsonObject();
+ }
+
+ if (!json.isObject()) {
+ qDebug() << "getQJsonObjectFromJString - object expected.";
+ return QJsonObject();
+ }
+
+ return json.object();
+}
+
+QJniObject AndroidUtils::getActivity() {
+ return QNativeInterface::QAndroidApplication::context();
+}
+
+int AndroidUtils::GetSDKVersion() {
+ QJniEnvironment env;
+ jclass versionClass = env->FindClass("android/os/Build$VERSION");
+ jfieldID sdkIntFieldID = env->GetStaticFieldID(versionClass, "SDK_INT", "I");
+ int sdk = env->GetStaticIntField(versionClass, sdkIntFieldID);
+
+ return sdk;
+}
+
+QString AndroidUtils::GetManufacturer() {
+ QJniEnvironment env;
+ jclass buildClass = env->FindClass("android/os/Build");
+ jfieldID manuFacturerField =
+ env->GetStaticFieldID(buildClass, "MANUFACTURER", "Ljava/lang/String;");
+ jstring value =
+ (jstring)env->GetStaticObjectField(buildClass, manuFacturerField);
+
+ const char* buffer = env->GetStringUTFChars(value, nullptr);
+
+ if (!buffer) {
+ qDebug() << "Failed to fetch MANUFACTURER";
+ return QByteArray();
+ }
+
+ QString res(buffer);
+ qDebug() << "MANUFACTURER: " << res;
+ env->ReleaseStringUTFChars(value, buffer);
+ return res;
+}
+
+void AndroidUtils::runOnAndroidThreadSync(const std::function runnable) {
+ QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable)
+ .waitForFinished();
+}
+
+void AndroidUtils::runOnAndroidThreadAsync(const std::function runnable) {
+ QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable);
+}
+
+// Static
+// Creates a copy of the passed QByteArray in the JVM and passes back a ref
+jbyteArray AndroidUtils::tojByteArray(const QByteArray& data) {
+ QJniEnvironment env;
+ jbyteArray out = env->NewByteArray(data.size());
+ env->SetByteArrayRegion(out, 0, data.size(),
+ reinterpret_cast(data.constData()));
+ return out;
+}
diff --git a/client/platforms/android/androidutils.h b/client/platforms/android/androidutils.h
new file mode 100644
index 000000000..8559400c8
--- /dev/null
+++ b/client/platforms/android/androidutils.h
@@ -0,0 +1,49 @@
+/* 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 ANDROIDUTILS_H
+#define ANDROIDUTILS_H
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+class AndroidUtils final : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY_MOVE(AndroidUtils)
+
+public:
+ static QString GetDeviceName();
+
+ static int GetSDKVersion();
+ static QString GetManufacturer();
+
+ static AndroidUtils* instance();
+
+ static void dispatchToMainThread(std::function callback);
+
+ static QByteArray getQByteArrayFromJString(JNIEnv* env, jstring data);
+
+ static jbyteArray tojByteArray(const QByteArray& data);
+
+ static QString getQStringFromJString(JNIEnv* env, jstring data);
+
+ static QJsonObject getQJsonObjectFromJString(JNIEnv* env, jstring data);
+
+ static QJniObject getActivity();
+
+ static void runOnAndroidThreadSync(const std::function runnable);
+ static void runOnAndroidThreadAsync(const std::function runnable);
+
+private:
+ AndroidUtils(QObject* parent);
+ ~AndroidUtils();
+};
+
+#endif // ANDROIDUTILS_H
diff --git a/client/platforms/android/androidvpnactivity.cpp b/client/platforms/android/androidvpnactivity.cpp
new file mode 100644
index 000000000..0e8b9574e
--- /dev/null
+++ b/client/platforms/android/androidvpnactivity.cpp
@@ -0,0 +1,136 @@
+/* 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
+#include
+#include
+#include
+#include
+
+#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(handleBackButton)},
+ {"onServiceMessage", "(ILjava/lang/String;)V",
+ reinterpret_cast(onServiceMessage)},
+ {"qtOnServiceConnected", "()V",
+ reinterpret_cast(onServiceConnected)},
+ {"qtOnServiceDisconnected", "()V",
+ reinterpret_cast(onServiceDisconnected)},
+ };
+
+ QJniObject javaClass(CLASSNAME);
+ QJniEnvironment env;
+ jclass objectClass = env->GetObjectClass(javaClass.object());
+ 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(CLASSNAME, "connectService", "()V");
+}
+
+// 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;
+
+ QJniEnvironment env;
+ QJniObject::callStaticMethod(
+ CLASSNAME, "sendToService", "(ILjava/lang/String;)V",
+ static_cast(messageType),
+ QJniObject::fromString(data).object());
+}
+
+// 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::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();
+}
diff --git a/client/platforms/android/androidvpnactivity.h b/client/platforms/android/androidvpnactivity.h
new file mode 100644
index 000000000..49d1aae5e
--- /dev/null
+++ b/client/platforms/android/androidvpnactivity.h
@@ -0,0 +1,93 @@
+/* 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
+
+#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 recieve 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
+enum ServiceEvents {
+ // The Service has Accecpted 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 transfered 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;
+
+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();
+
+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);
+
+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);
+ void handleServiceMessage(int code, const QString& data);
+};
+
+#endif // ANDROIDVPNACTIVITY_H
diff --git a/client/protocols/android_vpnprotocol.cpp b/client/protocols/android_vpnprotocol.cpp
index d86a33424..521b08f2e 100644
--- a/client/protocols/android_vpnprotocol.cpp
+++ b/client/protocols/android_vpnprotocol.cpp
@@ -7,11 +7,7 @@
#include
#include
-#include
-
-
#include "android_vpnprotocol.h"
-#include "core/errorstrings.h"
#include "platforms/android/android_controller.h"
@@ -19,9 +15,7 @@
AndroidVpnProtocol::AndroidVpnProtocol(Proto protocol, const QJsonObject &configuration, QObject* parent)
: VpnProtocol(configuration, parent),
m_protocol(protocol)
-{
-
-}
+{ }
ErrorCode AndroidVpnProtocol::start()
{
@@ -35,3 +29,11 @@ void AndroidVpnProtocol::stop()
AndroidController::instance()->stop();
}
+void AndroidVpnProtocol::connectionDataUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4)
+{
+ quint64 rxBytes = totalRx.toLongLong();
+ quint64 txBytes = totalTx.toLongLong();
+
+ setBytesChanged(rxBytes, txBytes);
+}
+
diff --git a/client/protocols/android_vpnprotocol.h b/client/protocols/android_vpnprotocol.h
index cdcff2c6b..ea87679a6 100644
--- a/client/protocols/android_vpnprotocol.h
+++ b/client/protocols/android_vpnprotocol.h
@@ -1,16 +1,11 @@
#ifndef ANDROID_VPNPROTOCOL_H
#define ANDROID_VPNPROTOCOL_H
-#include
-#include
-
#include "vpnprotocol.h"
#include "protocols/protocols_defs.h"
using namespace amnezia;
-
-
class AndroidVpnProtocol : public VpnProtocol
{
Q_OBJECT
@@ -25,6 +20,9 @@ public:
signals:
+public slots:
+ void connectionDataUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4);
+
protected slots:
protected:
@@ -32,7 +30,6 @@ protected:
private:
Proto m_protocol;
-
};
#endif // ANDROID_VPNPROTOCOL_H
diff --git a/client/protocols/ios_vpnprotocol.h b/client/protocols/ios_vpnprotocol.h
index 7238fc36e..7f55be198 100644
--- a/client/protocols/ios_vpnprotocol.h
+++ b/client/protocols/ios_vpnprotocol.h
@@ -36,6 +36,7 @@ public:
void cleanupBackendLogs();
signals:
+ void newTransmitedDataCount(quint64 rxBytes, quint64 txBytes);
protected slots:
diff --git a/client/protocols/ios_vpnprotocol.mm b/client/protocols/ios_vpnprotocol.mm
index 14b7ee9a8..f314f5fbc 100644
--- a/client/protocols/ios_vpnprotocol.mm
+++ b/client/protocols/ios_vpnprotocol.mm
@@ -25,8 +25,10 @@ Proto currentProto = amnezia::Proto::Any;
}
IOSVpnProtocol::IOSVpnProtocol(Proto proto, const QJsonObject &configuration, QObject* parent)
-: VpnProtocol(configuration, parent),
-m_protocol(proto) {}
+: VpnProtocol(configuration, parent), m_protocol(proto)
+{
+ connect(this, &IOSVpnProtocol::newTransmitedDataCount, this, &IOSVpnProtocol::setBytesChanged);
+}
IOSVpnProtocol* IOSVpnProtocol::instance() {
return s_instance;
@@ -207,8 +209,7 @@ void IOSVpnProtocol::checkStatus()
qDebug() << "ServerIpv4Gateway:" << QString::fromNSString(serverIpv4Gateway)
<< "DeviceIpv4Address:" << QString::fromNSString(deviceIpv4Address)
<< "RxBytes:" << rxBytes << "TxBytes:" << txBytes;
- emit bytesChanged(rxBytes, txBytes);
-
+ emit newTransmitedDataCount(rxBytes, txBytes);
}];
}
diff --git a/client/protocols/vpnprotocol.cpp b/client/protocols/vpnprotocol.cpp
index 2d304cdf7..a8f392e90 100644
--- a/client/protocols/vpnprotocol.cpp
+++ b/client/protocols/vpnprotocol.cpp
@@ -67,7 +67,10 @@ VpnProtocol::VpnConnectionState VpnProtocol::connectionState() const
void VpnProtocol::setBytesChanged(quint64 receivedBytes, quint64 sentBytes)
{
- emit bytesChanged(receivedBytes - m_receivedBytes, sentBytes - m_sentBytes);
+ quint64 rxDiff = receivedBytes - m_receivedBytes;
+ quint64 txDiff = sentBytes - m_sentBytes;
+
+ emit bytesChanged(rxDiff, txDiff);
m_receivedBytes = receivedBytes;
m_sentBytes = sentBytes;
diff --git a/client/ui/pages_logic/QrDecoderLogic.cpp b/client/ui/pages_logic/QrDecoderLogic.cpp
index 0c24ca1cc..764a6e4b3 100644
--- a/client/ui/pages_logic/QrDecoderLogic.cpp
+++ b/client/ui/pages_logic/QrDecoderLogic.cpp
@@ -3,10 +3,6 @@
#include "ui/uilogic.h"
#include "ui/pages_logic/StartPageLogic.h"
-#if defined(Q_OS_ANDROID)
-#include "android_controller.h"
-#endif
-
using namespace amnezia;
using namespace PageEnumNS;
diff --git a/client/ui/pages_logic/ServerSettingsLogic.cpp b/client/ui/pages_logic/ServerSettingsLogic.cpp
index 22555a917..32a62e089 100644
--- a/client/ui/pages_logic/ServerSettingsLogic.cpp
+++ b/client/ui/pages_logic/ServerSettingsLogic.cpp
@@ -2,7 +2,6 @@
#include "vpnconnection.h"
#include "../uilogic.h"
-#include "ServerListLogic.h"
#include "ShareConnectionLogic.h"
#include "VpnLogic.h"
@@ -11,9 +10,7 @@
#include
#if defined(Q_OS_ANDROID)
-#include
-#include
-#include
+#include "../../platforms/android/androidutils.h"
#endif
ServerSettingsLogic::ServerSettingsLogic(UiLogic *logic, QObject *parent):
@@ -24,9 +21,7 @@ ServerSettingsLogic::ServerSettingsLogic(UiLogic *logic, QObject *parent):
m_pushButtonShareFullVisible{true},
m_pushButtonClearText{tr("Clear server from Amnezia software")},
m_pushButtonClearClientCacheText{tr("Clear client cached profile")}
-{
-
-}
+{ }
void ServerSettingsLogic::onUpdatePage()
{
@@ -140,7 +135,7 @@ void ServerSettingsLogic::onLineEditDescriptionEditingFinished()
#if defined(Q_OS_ANDROID)
/* Auth result handler for Android */
-void authResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data)
+void authResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data)
{
qDebug() << "receiverRequestCode" << receiverRequestCode << "resultCode" << resultCode;
@@ -155,16 +150,17 @@ void ServerSettingsLogic::onPushButtonShareFullClicked()
{
#if defined(Q_OS_ANDROID)
/* We use builtin keyguard for ssh key export protection on Android */
- auto appContext = QtAndroid::androidActivity().callObjectMethod(
+ QJniObject activity = AndroidUtils::getActivity();
+ auto appContext = activity.callObjectMethod(
"getApplicationContext", "()Landroid/content/Context;");
if (appContext.isValid()) {
QAndroidActivityResultReceiver *receiver = new authResultReceiver(uiLogic(), uiLogic()->selectedServerIndex);
- auto intent = QAndroidJniObject::callStaticObjectMethod(
+ auto intent = QJniObject::callStaticObjectMethod(
"org/amnezia/vpn/AuthHelper", "getAuthIntent",
"(Landroid/content/Context;)Landroid/content/Intent;", appContext.object());
if (intent.isValid()) {
if (intent.object() != nullptr) {
- QtAndroid::startActivity(intent.object(), 1, receiver);
+ QtAndroidPrivate::startActivity(intent.object(), 1, receiver);
}
} else {
uiLogic()->pageLogic()->updateSharingPage(uiLogic()->selectedServerIndex, DockerContainer::None);
diff --git a/client/ui/pages_logic/ServerSettingsLogic.h b/client/ui/pages_logic/ServerSettingsLogic.h
index e2d574224..d561ec48a 100644
--- a/client/ui/pages_logic/ServerSettingsLogic.h
+++ b/client/ui/pages_logic/ServerSettingsLogic.h
@@ -4,7 +4,8 @@
#include "PageLogicBase.h"
#if defined(Q_OS_ANDROID)
-#include
+#include
+#include
#endif
class UiLogic;
@@ -52,7 +53,7 @@ public:
~authResultReceiver() {}
public:
- void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) override;
+ void handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) override;
private:
int m_serverIndex;
diff --git a/client/ui/pages_logic/StartPageLogic.cpp b/client/ui/pages_logic/StartPageLogic.cpp
index 76ba56fd6..3c15199de 100644
--- a/client/ui/pages_logic/StartPageLogic.cpp
+++ b/client/ui/pages_logic/StartPageLogic.cpp
@@ -11,8 +11,8 @@
#include
#ifdef Q_OS_ANDROID
-#include
-#include "platforms/android/android_controller.h"
+#include
+#include "../../platforms/android/androidutils.h"
#endif
namespace {
@@ -56,8 +56,9 @@ StartPageLogic::StartPageLogic(UiLogic *logic, QObject *parent):
{
#ifdef Q_OS_ANDROID
// Set security screen for Android app
- QtAndroid::runOnAndroidThread([]() {
- QAndroidJniObject window = QtAndroid::androidActivity().callObjectMethod("getWindow", "()Landroid/view/Window;");
+ AndroidUtils::runOnAndroidThreadSync([]() {
+ QJniObject activity = AndroidUtils::getActivity();
+ QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
if (window.isValid()){
const int FLAG_SECURE = 8192;
window.callMethod("addFlags", "(I)V", FLAG_SECURE);
diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp
index 07d81caf7..4ff67a070 100644
--- a/client/vpnconnection.cpp
+++ b/client/vpnconnection.cpp
@@ -19,7 +19,7 @@
#endif
#ifdef Q_OS_ANDROID
-#include "android_controller.h"
+#include "../../platforms/android/android_controller.h"
#include "protocols/android_vpnprotocol.h"
#endif
@@ -36,8 +36,6 @@ VpnConnection::VpnConnection(std::shared_ptr settings,
m_settings(settings),
m_configurator(configurator),
m_serverController(serverController),
- m_receivedBytes(0),
- m_sentBytes(0),
m_isIOSConnected(false)
{
}
@@ -52,11 +50,7 @@ VpnConnection::~VpnConnection()
void VpnConnection::onBytesChanged(quint64 receivedBytes, quint64 sentBytes)
{
- emit bytesChanged(receivedBytes - m_receivedBytes, sentBytes - m_sentBytes);
-
- m_receivedBytes = receivedBytes;
- m_sentBytes = sentBytes;
-
+ emit bytesChanged(receivedBytes, sentBytes);
}
void VpnConnection::onConnectionStateChanged(VpnProtocol::VpnConnectionState state)
@@ -362,6 +356,7 @@ void VpnConnection::connectToVpn(int serverIndex,
Proto proto = ContainerProps::defaultProtocol(container);
AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration);
connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, &AndroidVpnProtocol::setConnectionState);
+ connect(AndroidController::instance(), &AndroidController::statusUpdated, androidVpnProtocol, &AndroidVpnProtocol::connectionDataUpdated);
m_vpnProtocol.reset(androidVpnProtocol);
#elif defined Q_OS_IOS
diff --git a/client/vpnconnection.h b/client/vpnconnection.h
index c8ebdfad0..3a0d40646 100644
--- a/client/vpnconnection.h
+++ b/client/vpnconnection.h
@@ -93,8 +93,6 @@ private:
QJsonObject m_vpnConfiguration;
QJsonObject m_routeMode;
QString m_remoteAddress;
- quint64 m_receivedBytes;
- quint64 m_sentBytes;
bool m_isIOSConnected; //remove later move to isConnected,
#ifdef AMNEZIA_DESKTOP
diff --git a/deploy/build_android.sh b/deploy/build_android.sh
index e87993941..4357034fc 100644
--- a/deploy/build_android.sh
+++ b/deploy/build_android.sh
@@ -20,46 +20,39 @@ APP_DOMAIN=org.amneziavpn.package
OUT_APP_DIR=$BUILD_DIR/client
BUNDLE_DIR=$OUT_APP_DIR/$APP_FILENAME
-INSTALLER_DATA_DIR=$BUILD_DIR/installer/packages/$APP_DOMAIN/data
-INSTALLER_BUNDLE_DIR=$BUILD_DIR/installer/$APP_FILENAME
-
-PRO_FILE_PATH=$PROJECT_DIR/$APP_NAME.pro
-QMAKE_STASH_FILE=$PROJECT_DIR/.qmake_stash
-
# Seacrh Qt
if [ -z "${QT_VERSION+x}" ]; then
-QT_VERSION=5.15.2;
-QT_BIN_DIR=$HOME/Qt/$QT_VERSION/android/bin
+QT_VERSION=6.4.1;
+QT_BIN_DIR=$HOME/Qt/$QT_VERSION/$ANDROID_CURRENT_ARCH/bin
fi
echo "Using Qt in $QT_BIN_DIR"
echo "Using Android SDK in $ANDROID_SDK_ROOT"
echo "Using Android NDK in $ANDROID_NDK_ROOT"
-# Checking env
-$QT_BIN_DIR/qmake -v
-$ANDROID_NDK_HOME/prebuilt/linux-x86_64/bin/make -v
-
# Build App
echo "Building App..."
cd $BUILD_DIR
-$QT_BIN_DIR/qmake -r -spec android-clang CONFIG+=qtquickcompiler ANDROID_ABIS="armeabi-v7a arm64-v8a x86 x86_64" $PROJECT_DIR/AmneziaVPN.pro
-echo "Executing make... may take long time"
-$ANDROID_NDK_HOME/prebuilt/linux-x86_64/bin/make -j2
-echo "Make install..."
-$ANDROID_NDK_HOME/prebuilt/linux-x86_64/bin/make install INSTALL_ROOT=android
-echo "Build OK"
+echo "HOST Qt: $QT_HOST_PATH"
-echo "............Deploy.................."
+$QT_BIN_DIR/qt-cmake -S $PROJECT_DIR \
+ -DQT_NO_GLOBAL_APK_TARGET_PART_OF_ALL="ON" \
+ -DQT_HOST_PATH=$QT_HOST_PATH \
+ -DCMAKE_BUILD_TYPE="Release"
+
+cmake --build . --config release
+
+echo "............APK generation.................."
cd $OUT_APP_DIR
-$QT_BIN_DIR/androiddeployqt \
- --output $OUT_APP_DIR/android \
+$QT_HOST_PATH/bin/androiddeployqt \
+ --output $OUT_APP_DIR/android-build \
--gradle \
--release \
- --input android-AmneziaVPN-deployment-settings.json
-
+ --input android-AmneziaVPN-deployment-settings.json \
+ --android-platform android-31
+
echo "............Copy apk.................."
-cp $OUT_APP_DIR/android/build/outputs/apk/release/android-release-unsigned.apk \
+cp $OUT_APP_DIR/android-build/build/outputs/apk/release/android-build-release-unsigned.apk \
$PROJECT_DIR/AmneziaVPN-release-unsigned.apk