Merge pull request #152 from amnezia-vpn/feature/android_qt6_moving

Moving to Qt 6 and cmake on Android client
This commit is contained in:
pokamest 2023-01-19 12:51:02 +00:00 committed by GitHub
commit 18d89e9cad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 1267 additions and 573 deletions

View file

@ -255,7 +255,7 @@ jobs:
host: 'linux' host: 'linux'
target: 'desktop' target: 'desktop'
arch: 'gcc_64' arch: 'gcc_64'
modules: 'qtremoteobjects qt5compat' modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
setup-python: 'true' setup-python: 'true'
set-env: 'true' set-env: 'true'
@ -268,7 +268,7 @@ jobs:
host: 'linux' host: 'linux'
target: 'android' target: 'android'
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
modules: 'qtremoteobjects qt5compat' modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
setup-python: 'true' setup-python: 'true'
set-env: 'true' set-env: 'true'
@ -287,17 +287,19 @@ jobs:
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '8' java-version: '11'
- name: 'Build project' - name: 'Build project'
run: | run: |
export NDK_VERSION=21d export QT_HOST_PATH="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64"
export ANDROID_NDK_PLATFORM=android-21 export NDK_VERSION=23c
export ANDROID_NDK_PLATFORM=android-23
export ANDROID_NDK_HOME=${{ runner.temp }}/android-ndk-r${NDK_VERSION} export ANDROID_NDK_HOME=${{ runner.temp }}/android-ndk-r${NDK_VERSION}
export ANDROID_NDK_ROOT=$ANDROID_NDK_HOME export ANDROID_NDK_ROOT=$ANDROID_NDK_HOME
export ANDROID_CURRENT_ARCH=${{ matrix.arch }}
if [ ! -f $ANDROID_NDK_ROOT/ndk-build ]; then 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 ; unzip -q -d ${{ runner.temp }} ${{ runner.temp }}/ndk.zip ;
fi fi

142
.github/workflows/tag-deploy.yml vendored Normal file
View file

@ -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

View file

@ -3,6 +3,10 @@ cmake_minimum_required(VERSION 3.23.0 FATAL_ERROR)
set(PROJECT AmneziaVPN) set(PROJECT AmneziaVPN)
project(${PROJECT}) project(${PROJECT})
if(ANDROID)
set(QT_ANDROID_BUILD_ALL_ABIS ON)
endif()
add_subdirectory(client) add_subdirectory(client)
if(NOT IOS AND NOT ANDROID) if(NOT IOS AND NOT ANDROID)

View file

@ -38,11 +38,13 @@ if(ANDROID)
# We need to include qtprivate api's # We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api # As QAndroidBinder is not yet implemented with a public api
set(LIBS ${LIBS} Qt6::CorePrivate) set(LIBS ${LIBS} Qt6::CorePrivate)
set(ANDROID_ABIS ANDROID_TARGET_ARCH)
link_directories(${CMAKE_CURRENT_LIST_DIR}/android/${ANDROID_TARGET_ARCH}) set(abi ${CMAKE_ANDROID_ARCH_ABI})
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) 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() endif()
if(IOS) if(IOS)
@ -66,8 +68,4 @@ if(IOS)
include_directories(${CMAKE_CURRENT_LIST_DIR}/ios/iphone) include_directories(${CMAKE_CURRENT_LIST_DIR}/ios/iphone)
set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/ios/iphone/botan_all.h) set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/ios/iphone/botan_all.h)
set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/ios/iphone/botan_all.cpp) set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/ios/iphone/botan_all.cpp)
endif() endif()

@ -1 +1 @@
Subproject commit f197cdb935b0cfd9881fdc6860874cb8379d1238 Subproject commit c6f0b66318f8da6917fb4681103f7303b1836194

View file

@ -266,7 +266,6 @@ if(ANDROID)
# We need to include qtprivate api's # We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api # As QAndroidBinder is not yet implemented with a public api
set(LIBS ${LIBS} Qt6::CorePrivate) set(LIBS ${LIBS} Qt6::CorePrivate)
set(ANDROID_ABIS ANDROID_TARGET_ARCH)
add_compile_definitions(MVPN_ANDROID) add_compile_definitions(MVPN_ANDROID)
@ -275,12 +274,16 @@ if(ANDROID)
set(HEADERS ${HEADERS} set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_controller.h ${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_controller.h
${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_notificationhandler.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 ${CMAKE_CURRENT_LIST_DIR}/protocols/android_vpnprotocol.h
) )
set(SOURCES ${SOURCES} 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/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 ${CMAKE_CURRENT_LIST_DIR}/protocols/android_vpnprotocol.cpp
) )
endif() 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/NotificationUtil.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/OpenVPNThreadv3.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/Prefs.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VpnLogger.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/VPNService.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VpnServiceBinder.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/AmneziaApp.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/PackageManagerHelper.java ${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/VPNActivity.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNApplication.java ${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_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNPermissionHelper.kt
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
) )
set_property(TARGET ${PROJECT} set_property(TARGET ${PROJECT} PROPERTY
QT_ANDROID_PACKAGE_SOURCE_DIR QT_ANDROID_PACKAGE_SOURCE_DIR
${CMAKE_CURRENT_LIST_DIR}/android ${CMAKE_CURRENT_LIST_DIR}/android
) )
foreach(abi IN ANDROID_ABIS) foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
if(ANDROID_TARGET_ARCH EQUAL ${abi}) if(CMAKE_ANDROID_ARCH_ABI STREQUAL ${abi})
set(LIBS ${LIBS} set(LIBS ${LIBS}
${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/android/${abi}/libcrypto.a ${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/android/${abi}/libcrypto.a
${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/android/${abi}/libssl.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.so
${CMAKE_CURRENT_LIST_DIR}/android/lib/wireguard/${abi}/libwg-go.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/wireguard/${abi}/libwg-quick.so
${CMAKE_CURRENT_LIST_DIR}/android/lib/openvpn/${abi}/libjbcrypto.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}/libopenvpn.so
${CMAKE_CURRENT_LIST_DIR}/android/lib/openvpn/${abi}/libopvpnutil.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 ${CMAKE_CURRENT_LIST_DIR}/android/lib/openvpn/${abi}/libovpnexec.so
) )
endforeach() endforeach()
endif() endif()
target_link_libraries(${PROJECT} PRIVATE ${LIBS}) target_link_libraries(${PROJECT} PRIVATE ${LIBS})

View file

@ -21,13 +21,19 @@
Remove the comment if you do not require these default features. --> Remove the comment if you do not require these default features. -->
<!-- %%INSERT_FEATURES --> <!-- %%INSERT_FEATURES -->
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/> <supports-screens
android:largeScreens="true"
android:normalScreens="true"
android:anyDensity="true"
android:smallScreens="true"/>
<application <application
android:name=".qt.AmneziaApp" android:name=".qt.AmneziaApp"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:label="-- %%INSERT_APP_NAME%% --" android:label="-- %%INSERT_APP_NAME%% --"
android:extractNativeLibs="true" android:extractNativeLibs="true"
android:requestLegacyExternalStorage="true"
android:allowNativeHeapPointerTagging="false"
android:icon="@drawable/icon"> android:icon="@drawable/icon">
<activity <activity
@ -36,21 +42,22 @@
android:label="-- %%INSERT_APP_NAME%% --" android:label="-- %%INSERT_APP_NAME%% --"
android:screenOrientation="unspecified" android:screenOrientation="unspecified"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:taskAffinity="" android:exported="true">
android:theme="@style/splashScreenTheme">
<!-- android:theme="@style/splashScreenTheme"-->
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
<intent-filter android:label="AmneziaVPN"> <intent-filter android:label="AmneziaVPN">
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="file"/> <data android:scheme="file"/>
<data android:scheme="content"/> <data android:scheme="content"/>
<data android:mimeType= "*/*"/> <data android:mimeType="*/*"/>
<data android:host="*"/> <data android:host="*"/>
<data android:pathPattern=".*\\.vpn"/> <data android:pathPattern=".*\\.vpn"/>
<data android:pathPattern=".*\\..*\\.vpn"/> <data android:pathPattern=".*\\..*\\.vpn"/>
@ -58,14 +65,14 @@
<data android:pathPattern=".*\\..*\\..*\\..*\\.vpn"/> <data android:pathPattern=".*\\..*\\..*\\..*\\.vpn"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.vpn"/> <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.vpn"/>
</intent-filter> </intent-filter>
<intent-filter android:label="AmneziaVPN"> <intent-filter android:label="AmneziaVPN">
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="file"/> <data android:scheme="file"/>
<data android:scheme="content"/> <data android:scheme="content"/>
<data android:mimeType= "*/*"/> <data android:mimeType="*/*"/>
<data android:host="*"/> <data android:host="*"/>
<data android:pathPattern=".*\\.cfg"/> <data android:pathPattern=".*\\.cfg"/>
<data android:pathPattern=".*\\..*\\.cfg"/> <data android:pathPattern=".*\\..*\\.cfg"/>
@ -73,14 +80,14 @@
<data android:pathPattern=".*\\..*\\..*\\..*\\.cfg"/> <data android:pathPattern=".*\\..*\\..*\\..*\\.cfg"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.cfg"/> <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.cfg"/>
</intent-filter> </intent-filter>
<intent-filter android:label="AmneziaVPN"> <intent-filter android:label="AmneziaVPN">
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="file"/> <data android:scheme="file"/>
<data android:scheme="content"/> <data android:scheme="content"/>
<data android:mimeType= "*/*"/> <data android:mimeType="*/*"/>
<data android:host="*"/> <data android:host="*"/>
<data android:pathPattern=".*\\.conf"/> <data android:pathPattern=".*\\.conf"/>
<data android:pathPattern=".*\\..*\\.conf"/> <data android:pathPattern=".*\\..*\\.conf"/>
@ -88,102 +95,51 @@
<data android:pathPattern=".*\\..*\\..*\\..*\\.conf"/> <data android:pathPattern=".*\\..*\\..*\\..*\\.conf"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.conf"/> <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.conf"/>
</intent-filter> </intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<!-- Application arguments --> <meta-data
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ --> android:name="android.app.extract_android_style"
<!-- Application arguments --> android:value="minimal" />
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/> <meta-data
<meta-data android:name="android.app.repository" android:value="default"/> android:name="android.app.background_running"
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/> android:value="false"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package --> <meta-data
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/> android:name="android.app.arguments"
<!-- Run with local libs --> android:value="-- %%INSERT_APP_ARGUMENTS%% --" />
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
<!-- Used to specify custom system library path to run with local system libs -->
<!-- <meta-data android:name="android.app.system_libs_prefix" android:value="/system/lib/"/> -->
<!-- Messages maps -->
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<meta-data android:value="@string/unsupported_android_version" android:name="android.app.unsupported_android_version"/>
<!-- Messages maps -->
<!-- Splash screen -->
<!-- Orientation-specific (portrait/landscape) data is checked first. If not available for current orientation,
then android.app.splash_screen_drawable. For best results, use together with splash_screen_sticky and
use hideSplashScreen() with a fade-out animation from Qt Android Extras to hide the splash screen when you
are done populating your window with content. -->
<!-- meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/logo_portrait" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/logo_landscape" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
<!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ -->
<!-- Splash screen -->
<!-- Background running -->
<!-- Warning: changing this value to true may cause unexpected crashes if the
application still try to draw after
"applicationStateChanged(Qt::ApplicationSuspended)"
signal is sent! -->
<meta-data android:name="android.app.background_running" android:value="false"/>
<!-- Background running -->
<!-- auto screen scale factor -->
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
<!-- auto screen scale factor -->
<!-- extract android style -->
<!-- available android:values :
* default - In most cases this will be the same as "full", but it can also be something else if needed, e.g., for compatibility reasons
* full - useful QWidget & Quick Controls 1 apps
* minimal - useful for Quick Controls 2 apps, it is much faster than "full"
* none - useful for apps that don't use any of the above Qt modules
-->
<meta-data android:name="android.app.extract_android_style" android:value="default"/>
<!-- extract android style -->
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/splashscreen"/>
</activity> </activity>
<service <service
android:name=".VPNService" android:name=".VPNService"
android:permission="android.permission.BIND_VPN_SERVICE"
android:process=":QtOnlyProcess" android:process=":QtOnlyProcess"
android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="true"> android:exported="true">
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<intent-filter>
<action android:name="android.net.VpnService"/> <intent-filter>
</intent-filter> <action android:name="android.net.VpnService"/>
</intent-filter>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
</service> </service>
<service <service
android:name=".qt.VPNPermissionHelper" android:name=".qt.VPNPermissionHelper"
android:permission="android.permission.BIND_VPN_SERVICE" android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="true"> android:exported="true">
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
</service> </service>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices --> <!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="org.amnezia.vpn.fileprovider" android:authorities="org.amnezia.vpn.fileprovider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/fileprovider"/>
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/fileprovider"/>
</provider> </provider>
</application> </application>

View file

@ -20,7 +20,7 @@ buildscript {
} }
dependencies { 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.github.ben-manes:gradle-versions-plugin:0.21.0'
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.8.0' classpath 'com.vanniktech:gradle-maven-publish-plugin:0.8.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
@ -72,7 +72,11 @@ android {
compileSdkVersion androidCompileSdkVersion.toInteger() compileSdkVersion androidCompileSdkVersion.toInteger()
//buildToolsVersion '28.0.3' buildToolsVersion androidBuildToolsVersion
ndkVersion androidNdkVersion
// Extract native libraries from the APK
packagingOptions.jniLibs.useLegacyPackaging true
dexOptions { dexOptions {
javaMaxHeapSize "3g" javaMaxHeapSize "3g"
@ -81,9 +85,9 @@ android {
sourceSets { sourceSets {
main { main {
manifest.srcFile 'AndroidManifest.xml' manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] java.srcDirs = [qtAndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qt5AndroidDir + '/res', 'res'] res.srcDirs = [qtAndroidDir + '/res', 'res']
resources.srcDirs = ['resources'] resources.srcDirs = ['resources']
renderscript.srcDirs = ['src'] renderscript.srcDirs = ['src']
assets.srcDirs = ['assets'] assets.srcDirs = ['assets']
@ -139,6 +143,7 @@ android {
debug { debug {
//applicationIdSuffix ".debug" //applicationIdSuffix ".debug"
//versionNameSuffix "-debug" //versionNameSuffix "-debug"
minifyEnabled false
externalNativeBuild { externalNativeBuild {
cmake { cmake {
arguments "-DANDROID_PACKAGE_NAME=${groupName}", "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}" arguments "-DANDROID_PACKAGE_NAME=${groupName}", "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View file

@ -1,11 +1,6 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<resources> <resources>
<array name="qt_sources"> <!-- DO NOT EDIT THIS: This file is populated automatically by the deployment tool. -->
<item>https://download.qt.io/ministro/android/qt5/qt-5.14</item>
</array>
<!-- The following is handled automatically by the deployment tool. It should
not be edited manually. -->
<array name="bundled_libs"> <array name="bundled_libs">
<!-- %%INSERT_EXTRA_LIBS%% --> <!-- %%INSERT_EXTRA_LIBS%% -->
@ -19,4 +14,8 @@
<!-- %%INSERT_LOCAL_LIBS%% --> <!-- %%INSERT_LOCAL_LIBS%% -->
</array> </array>
<string name="static_init_classes"><!-- %%INSERT_INIT_CLASSES%% --></string>
<string name="use_local_qt_libs"><!-- %%USE_LOCAL_QT_LIBS%% --></string>
<string name="bundle_local_qt_libs"><!-- %%BUNDLE_LOCAL_QT_LIBS%% --></string>
<string name="system_libs_prefix"><!-- %%SYSTEM_LIBS_PREFIX%% --></string>
</resources> </resources>

View file

@ -3,7 +3,7 @@ package org.amnezia.vpn;
import android.content.Context; import android.content.Context;
import android.app.KeyguardManager; import android.app.KeyguardManager;
import android.content.Intent; 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; import static android.content.Context.KEYGUARD_SERVICE;

View file

@ -31,6 +31,31 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna
private var mAlreadyInitialised = false private var mAlreadyInitialised = false
private var mService: VPNService = service 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() { override fun run() {
val config: ClientAPI_Config = ClientAPI_Config() val config: ClientAPI_Config = ClientAPI_Config()

View file

@ -41,6 +41,7 @@ import java.io.Closeable
import java.io.File import java.io.File
import java.io.FileDescriptor import java.io.FileDescriptor
import java.io.IOException import java.io.IOException
import java.lang.Exception
import android.net.VpnService as BaseVpnService import android.net.VpnService as BaseVpnService
@ -153,31 +154,6 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
private var flags = 0 private var flags = 0
private var startId = 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() { fun init() {
if (mAlreadyInitialised) { if (mAlreadyInitialised) {
return return
@ -216,13 +192,6 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
override fun onBind(intent: Intent): IBinder { override fun onBind(intent: Intent): IBinder {
Log.v(tag, "Aman: onBind....................") 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) { when (mProtocol) {
"shadowsocks" -> { "shadowsocks" -> {
when (intent.action) { when (intent.action) {
@ -306,9 +275,16 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
return mConnectionTime return mConnectionTime
} }
var isUp: Boolean var isUp: Boolean = false
get() { get() {
return currentTunnelHandle >= 0 return when (mProtocol) {
"openvpn" -> {
field
}
else -> {
currentTunnelHandle >= 0
}
}
} }
set(value) { set(value) {
if (value) { if (value) {
@ -319,17 +295,52 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
mBinder.dispatchEvent(VPNServiceBinder.EVENTS.disconnected, "") mBinder.dispatchEvent(VPNServiceBinder.EVENTS.disconnected, "")
mConnectionTime = 0 mConnectionTime = 0
} }
val status: JSONObject val status: JSONObject
get() { get() {
val deviceIpv4: String = "" 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 { return JSONObject().apply {
putOpt("rx_bytes", getConfigValue("rx_bytes")) putOpt("rx_bytes", status.rxBytes)
putOpt("tx_bytes", getConfigValue("tx_bytes")) putOpt("tx_bytes", status.txBytes)
putOpt("endpoint", mConfig?.getJSONObject("server")?.getString("ipv4Gateway")) putOpt("endpoint", status.endpoint)
putOpt("deviceIpv4", mConfig?.getJSONObject("device")?.getString("ipv4Address")) 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. * Checks if the VPN Permission is given.
* If the permission is given, returns true * If the permission is given, returns true
@ -677,6 +688,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
private fun startOpenVpn() { private fun startOpenVpn() {
mOpenVPNThreadv3 = OpenVPNThreadv3(this) mOpenVPNThreadv3 = OpenVPNThreadv3(this)
Thread({ Thread({
mOpenVPNThreadv3?.run() mOpenVPNThreadv3?.run()
}).start() }).start()

View file

@ -33,6 +33,7 @@ class VPNServiceBinder(service: VPNService) : Binder() {
const val setNotificationText = 8 const val setNotificationText = 8
const val setFallBackNotification = 9 const val setFallBackNotification = 9
const val shareConfig = 10 const val shareConfig = 10
const val importConfig = 11
} }
/** /**
@ -75,13 +76,14 @@ class VPNServiceBinder(service: VPNService) : Binder() {
ACTIONS.resumeActivate -> { ACTIONS.resumeActivate -> {
// [data] is empty // [data] is empty
// Activate the current tunnel // Activate the current tunnel
try { Log.i(tag, "resume activate")
mResumeConfig?.let { this.mService.turnOn(it) } try {
mResumeConfig?.let { this.mService.turnOn(it) }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(tag, "An Error occurred while enabling the VPN: ${e.localizedMessage}") Log.e(tag, "An Error occurred while enabling the VPN: ${e.localizedMessage}")
} }
return true return true
} }
ACTIONS.deactivate -> { ACTIONS.deactivate -> {
// [data] here is empty // [data] here is empty
@ -90,6 +92,7 @@ class VPNServiceBinder(service: VPNService) : Binder() {
} }
ACTIONS.registerEventListener -> { ACTIONS.registerEventListener -> {
Log.i(tag, "register: start")
// [data] contains the Binder that we need to dispatch the Events // [data] contains the Binder that we need to dispatch the Events
val binder = data.readStrongBinder() val binder = data.readStrongBinder()
mListener = binder mListener = binder
@ -150,6 +153,23 @@ class VPNServiceBinder(service: VPNService) : Binder() {
return true 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 -> { IBinder.LAST_CALL_TRANSACTION -> {
Log.e(tag, "The OS Requested to shut down the VPN") Log.e(tag, "The OS Requested to shut down the VPN")
this.mService.turnOff() this.mService.turnOff()
@ -176,9 +196,12 @@ class VPNServiceBinder(service: VPNService) : Binder() {
try { try {
mListener?.let { mListener?.let {
if (it.isBinderAlive) { if (it.isBinderAlive) {
Log.i(tag, "Dispatching event: binder alive")
val data = Parcel.obtain() val data = Parcel.obtain()
data.writeByteArray(payload?.toByteArray(charset("UTF-8"))) data.writeByteArray(payload?.toByteArray(charset("UTF-8")))
it.transact(code, data, Parcel.obtain(), 0) it.transact(code, data, Parcel.obtain(), 0)
} else {
Log.i(tag, "Dispatching event: binder NOT alive")
} }
} }
} catch (e: DeadObjectException) { } catch (e: DeadObjectException) {
@ -197,23 +220,7 @@ class VPNServiceBinder(service: VPNService) : Binder() {
const val statisticUpdate = 3 const val statisticUpdate = 3
const val backendLogs = 4 const val backendLogs = 4
const val activationError = 5 const val activationError = 5
const val configImport = 6 const val permissionRequired = 6
} const val configImport = 7
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
}
} }
} }

View file

@ -3,8 +3,8 @@ package org.amnezia.vpn.qt
import android.content.res.Configuration import android.content.res.Configuration
import org.amnezia.vpn.shadowsocks.core.Core import org.amnezia.vpn.shadowsocks.core.Core
import org.amnezia.vpn.shadowsocks.core.VpnManager import org.amnezia.vpn.shadowsocks.core.VpnManager
import org.qtproject.qt5.android.bindings.QtActivity import org.qtproject.qt.android.bindings.QtActivity
import org.qtproject.qt5.android.bindings.QtApplication import org.qtproject.qt.android.bindings.QtApplication
import android.app.Application import android.app.Application
class AmneziaApp: Application() { class AmneziaApp: Application() {

View file

@ -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; package org.amnezia.vpn.qt;
import android.Manifest import android.Manifest
@ -20,18 +24,33 @@ import org.amnezia.vpn.VPNServiceBinder
import org.amnezia.vpn.IMPORT_COMMAND_CODE import org.amnezia.vpn.IMPORT_COMMAND_CODE
import org.amnezia.vpn.IMPORT_ACTION_CODE import org.amnezia.vpn.IMPORT_ACTION_CODE
import org.amnezia.vpn.IMPORT_CONFIG_KEY import org.amnezia.vpn.IMPORT_CONFIG_KEY
import org.qtproject.qt5.android.bindings.QtActivity import org.qtproject.qt.android.bindings.QtActivity
import java.io.* 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 configString: String? = null
private var vpnServiceBinder: Messenger? = null private var vpnServiceBinder: IBinder? = null
private var isBound = false private var isBound = false
private val TAG = "VPNActivity" private val TAG = "VPNActivity"
private val STORAGE_PERMISSION_CODE = 42 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?) { override fun onCreate(savedInstanceState: Bundle?) {
val newIntent = intent val newIntent = intent
@ -42,6 +61,48 @@ class VPNActivity : org.qtproject.qt5.android.bindings.QtActivity() {
} }
super.onCreate(savedInstanceState) 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) { override fun onNewIntent(newIntent: Intent) {
@ -63,19 +124,11 @@ class VPNActivity : org.qtproject.qt5.android.bindings.QtActivity() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
if (configString != null && !isBound) { if (configString != null && isBound) {
bindVpnService() sendImportConfigCommand()
} }
} }
override fun onPause() {
if (vpnServiceBinder != null && isBound) {
unbindService(connection)
isBound = false
}
super.onPause()
}
private fun isReadStorageAllowed(): Boolean { private fun isReadStorageAllowed(): Boolean {
val permissionStatus = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) val permissionStatus = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
return permissionStatus == PackageManager.PERMISSION_GRANTED 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) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Storage read permission granted") Log.d(TAG, "Storage read permission granted")
if (configString == null) {
configString = processIntent(intent, intent.action!!)
}
if (configString != null) { if (configString != null) {
bindVpnService() Log.d(TAG, "not empty")
sendImportConfigCommand()
} else {
Log.d(TAG, "empty")
} }
} else { } else {
Toast.makeText(this, "Oops you just denied the permission", Toast.LENGTH_LONG).show() 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? { private fun processIntent(intent: Intent, action: String): String? {
val scheme = intent.scheme 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 { private var connection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, binder: IBinder) { override fun onServiceConnected(className: ComponentName, binder: IBinder) {
vpnServiceBinder = Messenger(binder) vpnServiceBinder = binder
if (configString != null) { // This is called when the connection with the service has been
val msg: Message = Message.obtain(null, IMPORT_COMMAND_CODE, 0, 0) // established, giving us the object we can use to
val bundle = Bundle() // interact with the service. We are communicating with the
bundle.putString(IMPORT_CONFIG_KEY, configString!!) // service using a Messenger, so here we get a client-side
msg.data = bundle // representation of that from the raw IBinder object.
if (registerBinder()){
try { qtOnServiceConnected();
vpnServiceBinder?.send(msg) } else {
} catch (e: RemoteException) { qtOnServiceDisconnected();
e.printStackTrace() return
}
configString = null
} }
isBound = true isBound = true
@ -183,9 +244,75 @@ class VPNActivity : org.qtproject.qt5.android.bindings.QtActivity() {
override fun onServiceDisconnected(className: ComponentName) { override fun onServiceDisconnected(className: ComponentName) {
vpnServiceBinder = null vpnServiceBinder = null
isBound = false 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 { override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK && event.repeatCount == 0) { if (keyCode == KeyEvent.KEYCODE_BACK && event.repeatCount == 0) {
onBackPressed() onBackPressed()

View file

@ -5,7 +5,7 @@ import androidx.annotation.NonNull;
import org.amnezia.vpn.shadowsocks.core.Core; import org.amnezia.vpn.shadowsocks.core.Core;
import org.amnezia.vpn.shadowsocks.core.VpnManager; 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; private static VPNApplication instance;
@Override @Override

View file

@ -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
}
}

View file

@ -2,7 +2,7 @@ package org.ftylitak.qzxing;
import android.Manifest; import android.Manifest;
import android.content.pm.PackageManager; 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; import static org.ftylitak.qzxing.Utilities.REQUEST_CAMERA;
public class QZXingLiveActivity extends QtActivity { public class QZXingLiveActivity extends QtActivity {

View file

@ -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 equals(QT_MAJOR_VERSION, 6): QT += core5compat
TARGET = AmneziaVPN TARGET = AmneziaVPN
@ -241,14 +241,8 @@ android {
versionAtLeast(QT_VERSION, 6.0.0) { versionAtLeast(QT_VERSION, 6.0.0) {
# We need to include qtprivate api's # We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api # As QAndroidBinder is not yet implemented with a public api
QT+=core-private QT += core-private
ANDROID_ABIS=ANDROID_TARGET_ARCH ANDROID_ABIS = $$ANDROID_TARGET_ARCH
# for not changing qtkeychain sources for qt6
QT -= androidextras
}
else {
QT += androidextras
} }
DEFINES += MVPN_ANDROID DEFINES += MVPN_ANDROID
@ -258,13 +252,16 @@ android {
HEADERS += \ HEADERS += \
platforms/android/android_controller.h \ platforms/android/android_controller.h \
platforms/android/android_notificationhandler.h \ platforms/android/android_notificationhandler.h \
protocols/android_vpnprotocol.h protocols/android_vpnprotocol.h \
platforms/android/androidutils.h \
platforms/android/androidvpnactivity.h
SOURCES += \ SOURCES += \
platforms/android/android_controller.cpp \ platforms/android/android_controller.cpp \
platforms/android/android_notificationhandler.cpp \ platforms/android/android_notificationhandler.cpp \
protocols/android_vpnprotocol.cpp protocols/android_vpnprotocol.cpp \
platforms/android/androidutils.cpp \
platforms/android/androidvpnactivity.cpp
DISTFILES += \ DISTFILES += \
android/AndroidManifest.xml \ android/AndroidManifest.xml \
@ -293,6 +290,7 @@ android {
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
for (abi, ANDROID_ABIS): { for (abi, ANDROID_ABIS): {
equals(ANDROID_TARGET_ARCH,$$abi) { equals(ANDROID_TARGET_ARCH,$$abi) {
LIBS += $$PWD/3rd/OpenSSL/lib/android/$${abi}/libcrypto.a LIBS += $$PWD/3rd/OpenSSL/lib/android/$${abi}/libcrypto.a
LIBS += $$PWD/3rd/OpenSSL/lib/android/$${abi}/libssl.a LIBS += $$PWD/3rd/OpenSSL/lib/android/$${abi}/libssl.a

View file

@ -19,8 +19,8 @@ add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
set(LIBS ${LIBS} qt6keychain) set(LIBS ${LIBS} qt6keychain)
include_directories( include_directories(
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/include
${CLIENT_ROOT_DIR}/3rd/OpenSSL/include ${CLIENT_ROOT_DIR}/3rd/OpenSSL/include
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/include
${CLIENT_ROOT_DIR}/3rd/qtkeychain ${CLIENT_ROOT_DIR}/3rd/qtkeychain
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain ${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
) )

View file

@ -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 <QDebug> #include <QDebug>
#include <QHostAddress> #include <QHostAddress>
#include <QJsonArray> #include <QJsonArray>
@ -7,51 +11,131 @@
#include <QTextCodec> #include <QTextCodec>
#include <QTimer> #include <QTimer>
#include <QtCore/private/qandroidextras_p.h>
#include "android_controller.h" #include "android_controller.h"
#include "core/errorstrings.h" #include "private/qandroidextras_p.h"
#include "ui/pages_logic/StartPageLogic.h" #include "ui/pages_logic/StartPageLogic.h"
// Binder Codes for VPNServiceBinder #include "androidvpnactivity.h"
// See also - VPNServiceBinder.kt #include "androidutils.h"
// 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;
namespace { namespace {
AndroidController* s_instance = nullptr; AndroidController* s_instance = nullptr;
constexpr auto PERMISSIONHELPER_CLASS = constexpr auto PERMISSIONHELPER_CLASS =
"org/amnezia/vpn/qt/VPNPermissionHelper"; "org/amnezia/vpn/qt/VPNPermissionHelper";
} // namespace } // namespace
AndroidController::AndroidController(): AndroidController::AndroidController() : QObject()
m_binder(this)
{ {
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() { AndroidController* AndroidController::instance() {
if (!s_instance) s_instance = new AndroidController(); if (!s_instance) {
s_instance = new AndroidController();
}
return s_instance; return s_instance;
} }
@ -65,71 +149,43 @@ bool AndroidController::initialize(StartPageLogic *startPageLogic)
JNINativeMethod methods[]{{"startActivityForResult", JNINativeMethod methods[]{{"startActivityForResult",
"(Landroid/content/Intent;)V", "(Landroid/content/Intent;)V",
reinterpret_cast<void*>(startActivityForResult)}}; reinterpret_cast<void*>(startActivityForResult)}};
QAndroidJniObject javaClass(PERMISSIONHELPER_CLASS); QJniObject javaClass(PERMISSIONHELPER_CLASS);
QAndroidJniEnvironment env; QJniEnvironment env;
jclass objectClass = env->GetObjectClass(javaClass.object<jobject>()); jclass objectClass = env->GetObjectClass(javaClass.object<jobject>());
env->RegisterNatives(objectClass, methods, env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0]));
sizeof(methods) / sizeof(methods[0]));
env->DeleteLocalRef(objectClass); env->DeleteLocalRef(objectClass);
auto appContext = QtAndroid::androidActivity().callObjectMethod( AndroidVPNActivity::connectService();
"getApplicationContext", "()Landroid/content/Context;");
QAndroidJniObject::callStaticMethod<void>( return true;
"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;
} }
ErrorCode AndroidController::start() ErrorCode AndroidController::start()
{ {
//qDebug().noquote() << "AndroidController::start" << QJsonDocument(m_rawConfig).toJson();
qDebug() << "Prompting for VPN permission"; qDebug() << "Prompting for VPN permission";
auto appContext = QtAndroid::androidActivity().callObjectMethod( QJniObject activity = AndroidUtils::getActivity();
auto appContext = activity.callObjectMethod(
"getApplicationContext", "()Landroid/content/Context;"); "getApplicationContext", "()Landroid/content/Context;");
QAndroidJniObject::callStaticMethod<void>( QJniObject::callStaticMethod<void>(
PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V", PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V",
appContext.object()); appContext.object());
QAndroidParcel sendData; QJsonDocument doc(m_vpnConfig);
sendData.writeData(QJsonDocument(m_vpnConfig).toJson()); AndroidVPNActivity::sendToService(ServiceAction::ACTION_ACTIVATE, doc.toJson());
bool activateResult = false;
while (!activateResult){
activateResult = m_serviceBinder.transact(ACTION_ACTIVATE, sendData, nullptr);
}
return activateResult ? NoError : UnknownError; return NoError;
} }
void AndroidController::stop() { void AndroidController::stop() {
qDebug() << "AndroidController::stop"; qDebug() << "AndroidController::stop";
// if (reason != ReasonNone) { AndroidVPNActivity::sendToService(ServiceAction::ACTION_DEACTIVATE, QString());
// // 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);
} }
// Activates the tunnel that is currently set // Activates the tunnel that is currently set
// in the VPN Service // in the VPN Service
void AndroidController::resumeStart() { void AndroidController::resumeStart() {
QAndroidParcel nullData; AndroidVPNActivity::sendToService(ServiceAction::ACTION_RESUME_ACTIVATE, QString());
m_serviceBinder.transact(ACTION_RESUME_ACTIVATE, nullData, nullptr);
} }
/* /*
@ -138,14 +194,13 @@ void AndroidController::resumeStart() {
void AndroidController::setNotificationText(const QString& title, void AndroidController::setNotificationText(const QString& title,
const QString& message, const QString& message,
int timerSec) { int timerSec) {
QJsonObject args; QJsonObject args;
args["title"] = title; args["title"] = title;
args["message"] = message; args["message"] = message;
args["sec"] = timerSec; args["sec"] = timerSec;
QJsonDocument doc(args); QJsonDocument doc(args);
QAndroidParcel data;
data.writeData(doc.toJson()); AndroidVPNActivity::sendToService(ServiceAction::ACTION_SET_NOTIFICATION_TEXT, doc.toJson());
m_serviceBinder.transact(ACTION_SET_NOTIFICATION_TEXT, data, nullptr);
} }
void AndroidController::shareConfig(const QString& configContent, const QString& suggestedName) { 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["data"] = configContent;
rootObject["suggestedName"] = suggestedName; rootObject["suggestedName"] = suggestedName;
QJsonDocument doc(rootObject); QJsonDocument doc(rootObject);
QAndroidParcel parcel;
parcel.writeData(doc.toJson()); AndroidVPNActivity::sendToService(ServiceAction::ACTION_SHARE_CONFIG, doc.toJson());
m_serviceBinder.transact(ACTION_SHARE_CONFIG, parcel, nullptr);
} }
/* /*
@ -164,64 +218,40 @@ void AndroidController::shareConfig(const QString& configContent, const QString&
* e.g via always-on vpn * e.g via always-on vpn
*/ */
void AndroidController::setFallbackConnectedNotification() { void AndroidController::setFallbackConnectedNotification() {
QJsonObject args; QJsonObject args;
args["title"] = tr("AmneziaVPN"); args["title"] = tr("AmneziaVPN");
//% "Ready for you to connect" //% "Ready for you to connect"
//: Refers to the app - which is currently running the background and waiting //: Refers to the app - which is currently running the background and waiting
args["message"] = tr("VPN Connected"); args["message"] = tr("VPN Connected");
QJsonDocument doc(args); QJsonDocument doc(args);
QAndroidParcel data;
data.writeData(doc.toJson()); AndroidVPNActivity::sendToService(ServiceAction::ACTION_SET_NOTIFICATION_FALLBACK, doc.toJson());
m_serviceBinder.transact(ACTION_SET_NOTIFICATION_FALLBACK, data, nullptr);
} }
void AndroidController::checkStatus() { void AndroidController::checkStatus() {
qDebug() << "check status"; qDebug() << "check status";
QAndroidParcel nullParcel; AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, QString());
m_serviceBinder.transact(ACTION_REQUEST_STATISTIC, nullParcel, nullptr);
} }
void AndroidController::getBackendLogs(std::function<void(const QString&)>&& a_callback) { void AndroidController::getBackendLogs(std::function<void(const QString&)>&& a_callback) {
qDebug() << "get logs"; qDebug() << "get logs";
m_logCallback = std::move(a_callback); m_logCallback = std::move(a_callback);
QAndroidParcel nullData, replyData;
m_serviceBinder.transact(ACTION_REQUEST_GET_LOG, nullData, &replyData); AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_GET_LOG, QString());
} }
void AndroidController::cleanupBackendLogs() { void AndroidController::cleanupBackendLogs() {
qDebug() << "cleanup logs"; qDebug() << "cleanup logs";
QAndroidParcel nullParcel; AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_CLEANUP_LOG, QString());
m_serviceBinder.transact(ACTION_REQUEST_CLEANUP_LOG, nullParcel, nullptr);
} }
void AndroidController::importConfig(const QString& data){ void AndroidController::importConfig(const QString& data){
m_startPageLogic->importConnectionFromCode(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 const QJsonObject &AndroidController::vpnConfig() const
{ {
return m_vpnConfig; return m_vpnConfig;
@ -232,86 +262,14 @@ void AndroidController::setVpnConfig(const QJsonObject &newVpnConfig)
m_vpnConfig = newVpnConfig; m_vpnConfig = newVpnConfig;
} }
void AndroidController::scheduleStatusCheckSlot()
/** {
* @brief AndroidController::VPNBinder::onTransact QTimer::singleShot(1000, [this]() {
* @param code the Event-Type we get From the VPNService See if (isConnected) {
* @param data - Might contain UTF-8 JSON in case the Event has a payload checkStatus();
* @param reply - always null emit scheduleStatusCheckSignal();
* @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());
} }
const int ACTIVITY_RESULT_OK = 0xffffffff; const int ACTIVITY_RESULT_OK = 0xffffffff;
@ -322,34 +280,34 @@ const int ACTIVITY_RESULT_OK = 0xffffffff;
*/ */
void AndroidController::startActivityForResult(JNIEnv *env, jobject, jobject intent) void AndroidController::startActivityForResult(JNIEnv *env, jobject, jobject intent)
{ {
qDebug() << "start activity"; qDebug() << "start vpnPermissionHelper";
Q_UNUSED(env); 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 = QtAndroidPrivate::startActivity(intent, 1337,
AndroidController::instance(); [](int receiverRequestCode, int resultCode,
if (!controller) { const QJniObject& data) {
return; // 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) { AndroidController* controller = AndroidController::instance();
qDebug() << "VPN PROMPT RESULT - Accepted"; if (!controller) {
controller->resumeStart(); return;
return; }
}
// If the request got rejected abort the current if (resultCode == ACTIVITY_RESULT_OK) {
// connection. qDebug() << "VPN PROMPT RESULT - Accepted";
qWarning() << "VPN PROMPT RESULT - Rejected"; controller->resumeStart();
emit controller->connectionStateChanged(VpnProtocol::Disconnected); return;
}); }
// If the request got rejected abort the current
// connection.
qWarning() << "VPN PROMPT RESULT - Rejected";
emit controller->connectionStateChanged(VpnProtocol::Disconnected);
});
return; return;
} }

View file

@ -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 #ifndef ANDROID_CONTROLLER_H
#define ANDROID_CONTROLLER_H #define ANDROID_CONTROLLER_H
#include <QtCore/private/qandroidextras_p.h> #include <QJniEnvironment>
#include <QJniObject>
#include "ui/uilogic.h"
#include "ui/pages_logic/StartPageLogic.h" #include "ui/pages_logic/StartPageLogic.h"
#include "protocols/vpnprotocol.h" #include "protocols/vpnprotocol.h"
using namespace amnezia; using namespace amnezia;
class AndroidController : public QObject, public QAndroidServiceConnection class AndroidController : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -34,10 +39,6 @@ public:
void cleanupBackendLogs(); void cleanupBackendLogs();
void importConfig(const QString& data); 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; const QJsonObject &vpnConfig() const;
void setVpnConfig(const QJsonObject &newVpnConfig); void setVpnConfig(const QJsonObject &newVpnConfig);
@ -52,13 +53,18 @@ signals:
void initialized(bool status, bool connected, void initialized(bool status, bool connected,
const QDateTime& connectionDate); const QDateTime& connectionDate);
void statusUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4);
void scheduleStatusCheckSignal();
protected slots: protected slots:
void scheduleStatusCheckSlot();
protected: protected:
private: private:
//Protocol m_protocol; bool m_init = false;
QJsonObject m_vpnConfig; QJsonObject m_vpnConfig;
StartPageLogic *m_startPageLogic; StartPageLogic *m_startPageLogic;
@ -66,24 +72,11 @@ private:
bool m_serviceConnected = false; bool m_serviceConnected = false;
std::function<void(const QString&)> m_logCallback; std::function<void(const QString&)> 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); static void startActivityForResult(JNIEnv* env, jobject /*thiz*/, jobject intent);
bool isConnected = false;
void scheduleStatusCheck();
}; };
#endif // ANDROID_CONTROLLER_H #endif // ANDROID_CONTROLLER_H

View file

@ -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 <QApplication>
#include <QJniEnvironment>
#include <QJniObject>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkCookieJar>
#include <QUrlQuery>
#include <QTimer>
#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<void()> 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<void()> runnable) {
QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable)
.waitForFinished();
}
void AndroidUtils::runOnAndroidThreadAsync(const std::function<void()> 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<const jbyte*>(data.constData()));
return out;
}

View file

@ -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 <jni.h>
#include <QJniEnvironment>
#include <QJniObject>
#include <QObject>
#include <QString>
#include <QUrl>
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<void()> 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<void()> runnable);
static void runOnAndroidThreadAsync(const std::function<void()> runnable);
private:
AndroidUtils(QObject* parent);
~AndroidUtils();
};
#endif // ANDROIDUTILS_H

View file

@ -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 <QApplication>
#include <QJniEnvironment>
#include <QJniObject>
#include <QJsonDocument>
#include <QJsonObject>
#include "androidutils.h"
#include "jni.h"
namespace {
AndroidVPNActivity* s_instance = nullptr;
constexpr auto CLASSNAME = "org.amnezia.vpn.qt.VPNActivity";
}
AndroidVPNActivity::AndroidVPNActivity() {
AndroidUtils::runOnAndroidThreadAsync([]() {
JNINativeMethod methods[]{
{"handleBackButton", "()Z", reinterpret_cast<bool*>(handleBackButton)},
{"onServiceMessage", "(ILjava/lang/String;)V",
reinterpret_cast<void*>(onServiceMessage)},
{"qtOnServiceConnected", "()V",
reinterpret_cast<void*>(onServiceConnected)},
{"qtOnServiceDisconnected", "()V",
reinterpret_cast<void*>(onServiceDisconnected)},
};
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");
}
// 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<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::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();
}

View file

@ -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 <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 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

View file

@ -7,11 +7,7 @@
#include <QTextCodec> #include <QTextCodec>
#include <QTimer> #include <QTimer>
#include <QtCore/private/qandroidextras_p.h>
#include "android_vpnprotocol.h" #include "android_vpnprotocol.h"
#include "core/errorstrings.h"
#include "platforms/android/android_controller.h" #include "platforms/android/android_controller.h"
@ -19,9 +15,7 @@
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) m_protocol(protocol)
{ { }
}
ErrorCode AndroidVpnProtocol::start() ErrorCode AndroidVpnProtocol::start()
{ {
@ -35,3 +29,11 @@ void AndroidVpnProtocol::stop()
AndroidController::instance()->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);
}

View file

@ -1,16 +1,11 @@
#ifndef ANDROID_VPNPROTOCOL_H #ifndef ANDROID_VPNPROTOCOL_H
#define ANDROID_VPNPROTOCOL_H #define ANDROID_VPNPROTOCOL_H
#include <QAndroidBinder>
#include <QAndroidServiceConnection>
#include "vpnprotocol.h" #include "vpnprotocol.h"
#include "protocols/protocols_defs.h" #include "protocols/protocols_defs.h"
using namespace amnezia; using namespace amnezia;
class AndroidVpnProtocol : public VpnProtocol class AndroidVpnProtocol : public VpnProtocol
{ {
Q_OBJECT Q_OBJECT
@ -25,6 +20,9 @@ public:
signals: signals:
public slots:
void connectionDataUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4);
protected slots: protected slots:
protected: protected:
@ -32,7 +30,6 @@ protected:
private: private:
Proto m_protocol; Proto m_protocol;
}; };
#endif // ANDROID_VPNPROTOCOL_H #endif // ANDROID_VPNPROTOCOL_H

View file

@ -36,6 +36,7 @@ public:
void cleanupBackendLogs(); void cleanupBackendLogs();
signals: signals:
void newTransmitedDataCount(quint64 rxBytes, quint64 txBytes);
protected slots: protected slots:

View file

@ -25,8 +25,10 @@ Proto currentProto = amnezia::Proto::Any;
} }
IOSVpnProtocol::IOSVpnProtocol(Proto proto, const QJsonObject &configuration, QObject* parent) IOSVpnProtocol::IOSVpnProtocol(Proto proto, const QJsonObject &configuration, QObject* parent)
: VpnProtocol(configuration, parent), : VpnProtocol(configuration, parent), m_protocol(proto)
m_protocol(proto) {} {
connect(this, &IOSVpnProtocol::newTransmitedDataCount, this, &IOSVpnProtocol::setBytesChanged);
}
IOSVpnProtocol* IOSVpnProtocol::instance() { IOSVpnProtocol* IOSVpnProtocol::instance() {
return s_instance; return s_instance;
@ -207,8 +209,7 @@ void IOSVpnProtocol::checkStatus()
qDebug() << "ServerIpv4Gateway:" << QString::fromNSString(serverIpv4Gateway) qDebug() << "ServerIpv4Gateway:" << QString::fromNSString(serverIpv4Gateway)
<< "DeviceIpv4Address:" << QString::fromNSString(deviceIpv4Address) << "DeviceIpv4Address:" << QString::fromNSString(deviceIpv4Address)
<< "RxBytes:" << rxBytes << "TxBytes:" << txBytes; << "RxBytes:" << rxBytes << "TxBytes:" << txBytes;
emit bytesChanged(rxBytes, txBytes); emit newTransmitedDataCount(rxBytes, txBytes);
}]; }];
} }

View file

@ -67,7 +67,10 @@ VpnProtocol::VpnConnectionState VpnProtocol::connectionState() const
void VpnProtocol::setBytesChanged(quint64 receivedBytes, quint64 sentBytes) 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_receivedBytes = receivedBytes;
m_sentBytes = sentBytes; m_sentBytes = sentBytes;

View file

@ -3,10 +3,6 @@
#include "ui/uilogic.h" #include "ui/uilogic.h"
#include "ui/pages_logic/StartPageLogic.h" #include "ui/pages_logic/StartPageLogic.h"
#if defined(Q_OS_ANDROID)
#include "android_controller.h"
#endif
using namespace amnezia; using namespace amnezia;
using namespace PageEnumNS; using namespace PageEnumNS;

View file

@ -2,7 +2,6 @@
#include "vpnconnection.h" #include "vpnconnection.h"
#include "../uilogic.h" #include "../uilogic.h"
#include "ServerListLogic.h"
#include "ShareConnectionLogic.h" #include "ShareConnectionLogic.h"
#include "VpnLogic.h" #include "VpnLogic.h"
@ -11,9 +10,7 @@
#include <QTimer> #include <QTimer>
#if defined(Q_OS_ANDROID) #if defined(Q_OS_ANDROID)
#include <QAndroidJniObject> #include "../../platforms/android/androidutils.h"
#include <QAndroidJniEnvironment>
#include <QtAndroid>
#endif #endif
ServerSettingsLogic::ServerSettingsLogic(UiLogic *logic, QObject *parent): ServerSettingsLogic::ServerSettingsLogic(UiLogic *logic, QObject *parent):
@ -24,9 +21,7 @@ ServerSettingsLogic::ServerSettingsLogic(UiLogic *logic, QObject *parent):
m_pushButtonShareFullVisible{true}, m_pushButtonShareFullVisible{true},
m_pushButtonClearText{tr("Clear server from Amnezia software")}, m_pushButtonClearText{tr("Clear server from Amnezia software")},
m_pushButtonClearClientCacheText{tr("Clear client cached profile")} m_pushButtonClearClientCacheText{tr("Clear client cached profile")}
{ { }
}
void ServerSettingsLogic::onUpdatePage() void ServerSettingsLogic::onUpdatePage()
{ {
@ -140,7 +135,7 @@ void ServerSettingsLogic::onLineEditDescriptionEditingFinished()
#if defined(Q_OS_ANDROID) #if defined(Q_OS_ANDROID)
/* Auth result handler for 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; qDebug() << "receiverRequestCode" << receiverRequestCode << "resultCode" << resultCode;
@ -155,16 +150,17 @@ void ServerSettingsLogic::onPushButtonShareFullClicked()
{ {
#if defined(Q_OS_ANDROID) #if defined(Q_OS_ANDROID)
/* We use builtin keyguard for ssh key export protection on 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;"); "getApplicationContext", "()Landroid/content/Context;");
if (appContext.isValid()) { if (appContext.isValid()) {
QAndroidActivityResultReceiver *receiver = new authResultReceiver(uiLogic(), uiLogic()->selectedServerIndex); QAndroidActivityResultReceiver *receiver = new authResultReceiver(uiLogic(), uiLogic()->selectedServerIndex);
auto intent = QAndroidJniObject::callStaticObjectMethod( auto intent = QJniObject::callStaticObjectMethod(
"org/amnezia/vpn/AuthHelper", "getAuthIntent", "org/amnezia/vpn/AuthHelper", "getAuthIntent",
"(Landroid/content/Context;)Landroid/content/Intent;", appContext.object()); "(Landroid/content/Context;)Landroid/content/Intent;", appContext.object());
if (intent.isValid()) { if (intent.isValid()) {
if (intent.object<jobject>() != nullptr) { if (intent.object<jobject>() != nullptr) {
QtAndroid::startActivity(intent.object<jobject>(), 1, receiver); QtAndroidPrivate::startActivity(intent.object<jobject>(), 1, receiver);
} }
} else { } else {
uiLogic()->pageLogic<ShareConnectionLogic>()->updateSharingPage(uiLogic()->selectedServerIndex, DockerContainer::None); uiLogic()->pageLogic<ShareConnectionLogic>()->updateSharingPage(uiLogic()->selectedServerIndex, DockerContainer::None);

View file

@ -4,7 +4,8 @@
#include "PageLogicBase.h" #include "PageLogicBase.h"
#if defined(Q_OS_ANDROID) #if defined(Q_OS_ANDROID)
#include <QAndroidActivityResultReceiver> #include <QJniObject>
#include <private/qandroidextras_p.h>
#endif #endif
class UiLogic; class UiLogic;
@ -52,7 +53,7 @@ public:
~authResultReceiver() {} ~authResultReceiver() {}
public: public:
void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) override; void handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) override;
private: private:
int m_serverIndex; int m_serverIndex;

View file

@ -11,8 +11,8 @@
#include <QStandardPaths> #include <QStandardPaths>
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include <QtAndroid> #include <QJniObject>
#include "platforms/android/android_controller.h" #include "../../platforms/android/androidutils.h"
#endif #endif
namespace { namespace {
@ -56,8 +56,9 @@ StartPageLogic::StartPageLogic(UiLogic *logic, QObject *parent):
{ {
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
// Set security screen for Android app // Set security screen for Android app
QtAndroid::runOnAndroidThread([]() { AndroidUtils::runOnAndroidThreadSync([]() {
QAndroidJniObject window = QtAndroid::androidActivity().callObjectMethod("getWindow", "()Landroid/view/Window;"); QJniObject activity = AndroidUtils::getActivity();
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
if (window.isValid()){ if (window.isValid()){
const int FLAG_SECURE = 8192; const int FLAG_SECURE = 8192;
window.callMethod<void>("addFlags", "(I)V", FLAG_SECURE); window.callMethod<void>("addFlags", "(I)V", FLAG_SECURE);

View file

@ -19,7 +19,7 @@
#endif #endif
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include "android_controller.h" #include "../../platforms/android/android_controller.h"
#include "protocols/android_vpnprotocol.h" #include "protocols/android_vpnprotocol.h"
#endif #endif
@ -36,8 +36,6 @@ VpnConnection::VpnConnection(std::shared_ptr<Settings> settings,
m_settings(settings), m_settings(settings),
m_configurator(configurator), m_configurator(configurator),
m_serverController(serverController), m_serverController(serverController),
m_receivedBytes(0),
m_sentBytes(0),
m_isIOSConnected(false) m_isIOSConnected(false)
{ {
} }
@ -52,11 +50,7 @@ VpnConnection::~VpnConnection()
void VpnConnection::onBytesChanged(quint64 receivedBytes, quint64 sentBytes) void VpnConnection::onBytesChanged(quint64 receivedBytes, quint64 sentBytes)
{ {
emit bytesChanged(receivedBytes - m_receivedBytes, sentBytes - m_sentBytes); emit bytesChanged(receivedBytes, sentBytes);
m_receivedBytes = receivedBytes;
m_sentBytes = sentBytes;
} }
void VpnConnection::onConnectionStateChanged(VpnProtocol::VpnConnectionState state) void VpnConnection::onConnectionStateChanged(VpnProtocol::VpnConnectionState state)
@ -362,6 +356,7 @@ void VpnConnection::connectToVpn(int serverIndex,
Proto proto = ContainerProps::defaultProtocol(container); Proto proto = ContainerProps::defaultProtocol(container);
AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration); AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration);
connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, &AndroidVpnProtocol::setConnectionState); connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, &AndroidVpnProtocol::setConnectionState);
connect(AndroidController::instance(), &AndroidController::statusUpdated, androidVpnProtocol, &AndroidVpnProtocol::connectionDataUpdated);
m_vpnProtocol.reset(androidVpnProtocol); m_vpnProtocol.reset(androidVpnProtocol);
#elif defined Q_OS_IOS #elif defined Q_OS_IOS

View file

@ -93,8 +93,6 @@ private:
QJsonObject m_vpnConfiguration; QJsonObject m_vpnConfiguration;
QJsonObject m_routeMode; QJsonObject m_routeMode;
QString m_remoteAddress; QString m_remoteAddress;
quint64 m_receivedBytes;
quint64 m_sentBytes;
bool m_isIOSConnected; //remove later move to isConnected, bool m_isIOSConnected; //remove later move to isConnected,
#ifdef AMNEZIA_DESKTOP #ifdef AMNEZIA_DESKTOP

View file

@ -20,46 +20,39 @@ APP_DOMAIN=org.amneziavpn.package
OUT_APP_DIR=$BUILD_DIR/client OUT_APP_DIR=$BUILD_DIR/client
BUNDLE_DIR=$OUT_APP_DIR/$APP_FILENAME 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 # Seacrh Qt
if [ -z "${QT_VERSION+x}" ]; then if [ -z "${QT_VERSION+x}" ]; then
QT_VERSION=5.15.2; QT_VERSION=6.4.1;
QT_BIN_DIR=$HOME/Qt/$QT_VERSION/android/bin QT_BIN_DIR=$HOME/Qt/$QT_VERSION/$ANDROID_CURRENT_ARCH/bin
fi fi
echo "Using Qt in $QT_BIN_DIR" echo "Using Qt in $QT_BIN_DIR"
echo "Using Android SDK in $ANDROID_SDK_ROOT" echo "Using Android SDK in $ANDROID_SDK_ROOT"
echo "Using Android NDK in $ANDROID_NDK_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 # Build App
echo "Building App..." echo "Building App..."
cd $BUILD_DIR 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 "HOST Qt: $QT_HOST_PATH"
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 "............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 cd $OUT_APP_DIR
$QT_BIN_DIR/androiddeployqt \ $QT_HOST_PATH/bin/androiddeployqt \
--output $OUT_APP_DIR/android \ --output $OUT_APP_DIR/android-build \
--gradle \ --gradle \
--release \ --release \
--input android-AmneziaVPN-deployment-settings.json --input android-AmneziaVPN-deployment-settings.json \
--android-platform android-31
echo "............Copy apk.................." 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 $PROJECT_DIR/AmneziaVPN-release-unsigned.apk