diff --git a/client/android/openvpn.tar.gz b/client/android/openvpn.tar.gz deleted file mode 100644 index 92a6a613..00000000 Binary files a/client/android/openvpn.tar.gz and /dev/null differ diff --git a/client/android/res/drawable/ic_amnezia_round.xml b/client/android/res/drawable/ic_amnezia_round.xml index c3158ddd..32df5ca4 100644 --- a/client/android/res/drawable/ic_amnezia_round.xml +++ b/client/android/res/drawable/ic_amnezia_round.xml @@ -1,11 +1,10 @@ - + android:width="256dp" + android:height="256dp" + android:viewportWidth="256" + android:viewportHeight="256"> + android:pathData="M121.6,3.7c-0.3,2.1 -0.8,9.1 -1.2,15.6 -0.3,6.4 -1,12 -1.5,12.3 -0.5,0.3 -0.9,1.3 -0.9,2.3 0,2.6 -4.8,7.1 -7.6,7.1 -1.3,-0 -2.4,0.4 -2.4,0.9 0,0.5 -1,1.1 -2.2,1.3 -1.6,0.2 -2.4,1.1 -2.6,3 -0.3,2.2 -0.9,2.7 -4.3,3.3 -2.1,0.4 -3.9,1 -3.9,1.5 0,0.4 -2.9,1.3 -6.5,2 -3.6,0.7 -6.5,1.6 -6.5,2.1 0,0.4 -2,1.1 -4.5,1.5 -4,0.6 -4.7,1.1 -6.5,4.8 -3.6,7.2 -5.1,9.6 -6,9.6 -0.5,-0 -1.7,1.4 -2.5,3.1 -1.4,2.5 -2.1,3 -4.7,2.7 -2.7,-0.3 -3.2,0.1 -4.6,3.6 -0.9,2.2 -3.1,5.3 -4.9,6.9 -1.8,1.6 -3.3,3.4 -3.3,4 0,0.5 -1.3,3.7 -2.9,7.1 -1.5,3.4 -3.1,7.1 -3.5,8.3 -0.4,1.3 -1.1,2.3 -1.5,2.3 -0.4,-0 -1.5,2.4 -2.6,5.2 -1,2.9 -2.1,5.7 -2.5,6.3 -0.4,0.5 -1.3,3.4 -2.1,6.2 -0.7,2.9 -1.6,5.3 -2,5.3 -1.1,-0 -3.1,15.4 -3.5,27.4l-0.4,11 -4.3,4.5c-2.4,2.6 -6.5,6.3 -9.2,8.4 -11.9,8.9 -13.2,11.3 -5.7,10.2 2.6,-0.4 5.2,-1.1 5.7,-1.5 1.6,-1.4 14.4,-3.2 16.9,-2.4 2.2,0.7 6.6,4.9 6.6,6.4 0,0.5 5.2,6.3 11.6,13l11.7,12.1 -0.6,9.6c-0.4,5.2 -1.1,10.1 -1.7,10.8 -1.2,1.4 -2.7,14.5 -1.6,14.5 0.3,-0 1.9,-1.1 3.5,-2.5 1.8,-1.7 3.4,-4.7 4.6,-8.8 0.9,-3.4 2.1,-7.1 2.6,-8.2 0.5,-1.1 1.3,-3.2 1.9,-4.8 0.5,-1.5 1.6,-2.7 2.5,-2.7 0.8,-0 1.5,-0.5 1.5,-1 0,-1.6 4.3,-1.2 8.6,0.9 12.6,6 15.3,7.1 17.7,7.1 1.5,-0 2.7,0.4 2.7,0.8 0,0.4 6.2,1.6 13.8,2.7 7.5,1 14.1,2.2 14.7,2.7 1.2,1.1 12.1,1 14.3,-0.1 0.9,-0.5 4.4,-1.5 7.7,-2.1 3.3,-0.7 7.8,-2 10,-3 2.3,-1 5.4,-2.2 7,-2.6 1.7,-0.4 3.5,-1 4.1,-1.5 1.5,-1.2 13.3,-3.9 17,-3.9 2.3,-0 3.3,0.5 3.7,1.9 0.2,1 1.7,2.5 3.2,3.2 1.8,1 3.4,3.1 4.9,6.7 2.3,5.7 8.7,13.2 11.2,13.2 1.5,-0 1.6,-1 0.9,-8.2 -0.4,-4.6 -1,-8.5 -1.4,-8.8 -0.8,-0.5 -2.6,-9.5 -3.7,-18.8 -0.7,-6 1.1,-15.1 3.2,-15.9 0.8,-0.3 1.4,-1.1 1.4,-1.8 0,-0.6 2.1,-3.7 4.8,-6.8 10.3,-12.3 9.6,-10.6 10.2,-25.8 0.3,-7.5 1.1,-14.3 1.7,-15 0.6,-0.8 1.4,-3.2 1.8,-5.4 0.6,-3.4 4.2,-10.3 7.4,-14.1 0.5,-0.6 1.2,-2.5 1.6,-4.2l0.7,-3.2 -6.8,-0 -6.7,-0 0.6,-4.7c0.3,-2.6 1,-4.9 1.6,-5.1 0.6,-0.2 1.1,-0.9 1.1,-1.5 0,-1.4 10.4,-11.7 11.8,-11.7 0.5,-0 1.6,-0.8 2.6,-1.9 0.9,-1 3.4,-2.6 5.6,-3.6 2.2,-1 4,-2.2 4,-2.7 0,-0.4 0.7,-0.8 1.5,-0.8 0.9,-0 1.5,-0.9 1.5,-2.1 0,-2 -0.3,-2.1 -5.7,-1.4 -3.2,0.4 -6.2,1 -6.8,1.5 -1.8,1.3 -14.5,3.3 -17.7,2.6 -1.7,-0.3 -4.1,-1.6 -5.2,-2.9 -3.9,-4 -10.4,-9.4 -12.2,-10.1 -1.3,-0.5 -1.5,-1.4 -1,-4.5 0.6,-4.1 -0.3,-5.1 -4.4,-5.1 -1.1,-0 -3.9,-1.9 -6.4,-4.3 -4.5,-4.3 -8,-7.2 -15.1,-12.6 -2.2,-1.7 -4.7,-3.6 -5.5,-4.3 -0.8,-0.7 -2.2,-1.8 -3,-2.3 -0.9,-0.6 -3.2,-2 -5.1,-3.2 -2.8,-1.9 -4.8,-2.3 -11.1,-2.3 -4.2,-0 -8,-0.5 -8.3,-1 -0.3,-0.6 -1.6,-1 -2.8,-1 -1.2,-0 -2.9,-0.6 -3.8,-1.3 -2.7,-2 -6.8,-10.9 -8,-17.2 -0.7,-3.3 -1.6,-6.5 -2,-7 -0.4,-0.6 -1.3,-3.3 -1.9,-6 -1.5,-7 -2.9,-9.5 -5.6,-9.5 -1.8,-0 -2.3,0.7 -2.8,3.7zM162.5,77.5c3.2,1.6 7.5,3.9 9.5,5.2 1.9,1.3 4.3,2.9 5.4,3.6 4.9,3.2 11.3,8.8 13.4,11.6 3.5,4.7 3.4,8.1 -0.3,10 -1.6,0.9 -3.5,2.1 -4.1,2.7 -0.7,0.6 -3.3,1.7 -5.8,2.4 -2.6,0.7 -4.9,1.7 -5.2,2.1 -0.3,0.5 -2.9,0.9 -5.9,0.9 -4.9,-0 -5.6,-0.3 -7.9,-3.3 -1.4,-1.8 -3.3,-5.3 -4.1,-7.8 -0.9,-2.4 -2.2,-5.8 -2.9,-7.4 -0.8,-1.7 -1.9,-5.7 -2.6,-9 -0.7,-3.3 -1.6,-6.2 -2,-6.5 -0.4,-0.3 -1,-2.2 -1.4,-4.4l-0.7,-3.9 4.3,0.5c2.4,0.3 7,1.8 10.3,3.3zM103,77.6c0,0.9 -0.6,3 -1.4,4.7 -1.2,2.7 -6.5,19.3 -8.3,26.2 -0.3,1.1 -0.9,2.4 -1.3,3 -0.4,0.5 -1.5,4.1 -2.5,8 -1,3.8 -2.1,7.4 -2.5,8 -0.4,0.5 -1.3,3.5 -2,6.5 -0.7,3 -1.6,5.7 -1.9,6 -0.3,0.3 -1.1,1.4 -1.7,2.4 -0.6,1.1 -1.9,2.4 -3,3 -1,0.6 -2.1,1.4 -2.4,1.7 -0.3,0.3 -2.2,1.2 -4.2,2 -2.1,0.7 -3.8,1.6 -3.8,2 0,0.4 -3.1,1.5 -7,2.5 -3.8,1 -7,2.2 -7,2.6 0,0.4 -1.9,0.8 -4.2,0.8l-4.1,-0 0.6,-9.8c0.9,-13 2.4,-18.3 8.4,-29.7 4.2,-7.9 9.4,-14.4 18.4,-22.9 11.2,-10.5 11.4,-10.6 12.8,-10.6 0.6,-0 1.4,-0.6 1.7,-1.3 0.2,-0.7 2.5,-2.2 4.9,-3.2 2.5,-1.1 4.5,-2.3 4.5,-2.8 0,-0.4 1.4,-0.7 3,-0.7 2.1,-0 3,0.5 3,1.6zM126.9,78.2c0.7,1.3 2.3,4.1 3.6,6.3 1.4,2.2 3.5,6 4.6,8.5 1.2,2.5 2.5,4.7 2.9,5 0.4,0.3 1.3,2.3 2,4.5 0.7,2.2 1.7,4.5 2.2,5.2 0.6,0.7 1.2,3.9 1.5,7.3 0.5,5.4 0.3,6.2 -1.8,8.4 -3.6,3.5 -9.8,6.3 -17,7.6 -3.5,0.6 -8.3,1.5 -10.7,2.1 -2.3,0.5 -5.4,0.9 -6.7,0.9 -2.8,-0 -4.3,-2.3 -2.8,-4.3 0.6,-0.6 1.6,-3.5 2.3,-6.2 0.7,-2.8 1.6,-5.5 2,-6 0.4,-0.6 1.5,-4.5 2.5,-8.8 1,-4.2 2.1,-7.7 2.5,-7.7 0.4,-0 1,-1.9 1.4,-4.2 0.4,-2.3 1.3,-5.6 2.1,-7.2 0.7,-1.7 1.9,-4.9 2.5,-7.1 2.1,-6.8 4.7,-8.4 6.9,-4.3zM207.6,123.1c0.8,1.3 0.7,26.1 -0.2,32.9 -0.3,2.5 -0.9,5.4 -1.4,6.5 -0.5,1.1 -1.3,4.8 -1.9,8.2 -0.7,3.4 -1.8,7.6 -2.7,9.5 -5.5,12.3 -7.1,13.3 -10.2,6.6 -5.1,-10.8 -7.1,-15.8 -7.7,-19.2 -0.4,-2 -1,-3.6 -1.4,-3.6 -0.4,-0 -1.3,-2.1 -2.1,-4.7 -0.7,-2.6 -1.9,-5.8 -2.6,-7.2 -1.6,-3.1 -3.9,-15.5 -3.1,-17.4 0.8,-2.1 14.2,-8.3 21.2,-9.8 3.3,-0.7 6.4,-1.6 7,-2.1 1.3,-1.2 4.3,-1 5.1,0.3zM156.2,145c3.7,2.9 5.5,7.4 6.7,17 0.8,6.4 1.9,10.1 4.5,15 7.8,14.8 9.8,19.6 10.3,24.7 0.7,6.6 -0.7,9.5 -5.3,11.3 -1.9,0.8 -3.4,1.7 -3.4,2.1 0,0.4 -2.1,1.5 -4.7,2.5 -2.7,1 -5,2.1 -5.3,2.5 -1.6,2.2 -7.7,2.9 -25.9,2.9 -19.7,-0 -28.9,-0.9 -34.6,-3.4 -1.6,-0.8 -4.5,-1.7 -6.3,-2.1 -1.8,-0.4 -5.2,-2.5 -7.5,-4.7l-4.2,-4 0.1,-8.9c0.1,-4.9 0.6,-9.5 1.2,-10.2 0.5,-0.6 1.3,-3 1.6,-5.1 1.1,-6.1 6.4,-14.5 13.6,-21.6 8,-7.8 8.8,-8.5 13.7,-10.7 2.1,-1 4,-2 4.3,-2.3 0.3,-0.3 2.5,-1.2 5,-1.9 2.5,-0.7 5.4,-1.7 6.5,-2.2 2.6,-1.2 13.3,-2.6 21.1,-2.8 4.7,-0.1 6.7,0.4 8.6,1.9zM68,173.6c0,3 -3.2,15.4 -4.1,15.9 -0.5,0.4 -0.9,1.6 -0.9,2.9 0,1.2 -0.6,3.1 -1.4,4.1 -1.4,1.9 -1.5,1.9 -4.9,0.1 -3.4,-1.9 -8.7,-10.3 -8.7,-13.8 0,-3.3 6.8,-8.8 10.9,-8.8 1.2,-0 2.1,-0.5 2.1,-1 0,-0.6 1.6,-1 3.5,-1 2.5,-0 3.5,0.4 3.5,1.6z" + android:fillColor="#000000" + android:strokeColor="#00000000"/> diff --git a/client/android/src/debug/res/mipmap-anydpi-v26/vpnicon.xml b/client/android/src/debug/res/mipmap-anydpi-v26/vpnicon.xml deleted file mode 100644 index 172bc624..00000000 --- a/client/android/src/debug/res/mipmap-anydpi-v26/vpnicon.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/client/android/src/debug/res/mipmap-anydpi-v26/vpnicon_round.xml b/client/android/src/debug/res/mipmap-anydpi-v26/vpnicon_round.xml deleted file mode 100644 index 7e0a9c7b..00000000 --- a/client/android/src/debug/res/mipmap-anydpi-v26/vpnicon_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/client/android/src/debug/res/mipmap-hdpi/vpnicon.png b/client/android/src/debug/res/mipmap-hdpi/vpnicon.png deleted file mode 100644 index fa762712..00000000 Binary files a/client/android/src/debug/res/mipmap-hdpi/vpnicon.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-hdpi/vpnicon_foreground.png b/client/android/src/debug/res/mipmap-hdpi/vpnicon_foreground.png deleted file mode 100644 index 849f6ffd..00000000 Binary files a/client/android/src/debug/res/mipmap-hdpi/vpnicon_foreground.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-hdpi/vpnicon_round.png b/client/android/src/debug/res/mipmap-hdpi/vpnicon_round.png deleted file mode 100644 index 0fc3bb7f..00000000 Binary files a/client/android/src/debug/res/mipmap-hdpi/vpnicon_round.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-mdpi/vpnicon.png b/client/android/src/debug/res/mipmap-mdpi/vpnicon.png deleted file mode 100644 index b5d3fe52..00000000 Binary files a/client/android/src/debug/res/mipmap-mdpi/vpnicon.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-mdpi/vpnicon_foreground.png b/client/android/src/debug/res/mipmap-mdpi/vpnicon_foreground.png deleted file mode 100644 index 198212fd..00000000 Binary files a/client/android/src/debug/res/mipmap-mdpi/vpnicon_foreground.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-mdpi/vpnicon_round.png b/client/android/src/debug/res/mipmap-mdpi/vpnicon_round.png deleted file mode 100644 index d6ce6de9..00000000 Binary files a/client/android/src/debug/res/mipmap-mdpi/vpnicon_round.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xhdpi/vpnicon.png b/client/android/src/debug/res/mipmap-xhdpi/vpnicon.png deleted file mode 100644 index b6f63054..00000000 Binary files a/client/android/src/debug/res/mipmap-xhdpi/vpnicon.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xhdpi/vpnicon_foreground.png b/client/android/src/debug/res/mipmap-xhdpi/vpnicon_foreground.png deleted file mode 100644 index bc00fe12..00000000 Binary files a/client/android/src/debug/res/mipmap-xhdpi/vpnicon_foreground.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xhdpi/vpnicon_round.png b/client/android/src/debug/res/mipmap-xhdpi/vpnicon_round.png deleted file mode 100644 index ba4aa7b7..00000000 Binary files a/client/android/src/debug/res/mipmap-xhdpi/vpnicon_round.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xxhdpi/vpnicon.png b/client/android/src/debug/res/mipmap-xxhdpi/vpnicon.png deleted file mode 100644 index e6d3c7ed..00000000 Binary files a/client/android/src/debug/res/mipmap-xxhdpi/vpnicon.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xxhdpi/vpnicon_foreground.png b/client/android/src/debug/res/mipmap-xxhdpi/vpnicon_foreground.png deleted file mode 100644 index 4f281d05..00000000 Binary files a/client/android/src/debug/res/mipmap-xxhdpi/vpnicon_foreground.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xxhdpi/vpnicon_round.png b/client/android/src/debug/res/mipmap-xxhdpi/vpnicon_round.png deleted file mode 100644 index deb1e253..00000000 Binary files a/client/android/src/debug/res/mipmap-xxhdpi/vpnicon_round.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon.png b/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon.png deleted file mode 100644 index be451734..00000000 Binary files a/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon_foreground.png b/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon_foreground.png deleted file mode 100644 index 8b8bad14..00000000 Binary files a/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon_foreground.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon_round.png b/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon_round.png deleted file mode 100644 index ec01eaee..00000000 Binary files a/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon_round.png and /dev/null differ diff --git a/client/android/src/debug/res/values/vpnicon_background.xml b/client/android/src/debug/res/values/vpnicon_background.xml deleted file mode 100644 index 5f343ea1..00000000 --- a/client/android/src/debug/res/values/vpnicon_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #000000 - \ No newline at end of file diff --git a/client/android/src/org/amnezia/vpn/VPNService.kt b/client/android/src/org/amnezia/vpn/VPNService.kt index b824610b..d6efc732 100644 --- a/client/android/src/org/amnezia/vpn/VPNService.kt +++ b/client/android/src/org/amnezia/vpn/VPNService.kt @@ -377,11 +377,11 @@ class VPNService : android.net.VpnService() { val wgConfig: String = wireguard_conf!!.toWgUserspaceString() val builder = Builder() setupBuilder(wireguard_conf, builder) - builder.setSession("mvpn0") + builder.setSession("avpn0") builder.establish().use { tun -> if (tun == null)return Log.i(tag, "Go backend " + wgVersion()) - currentTunnelHandle = wgTurnOn("mvpn0", tun.detachFd(), wgConfig) + currentTunnelHandle = wgTurnOn("avpn0", tun.detachFd(), wgConfig) } if (currentTunnelHandle < 0) { Log.e(tag, "Activation Error Code -> $currentTunnelHandle") diff --git a/client/client.pro b/client/client.pro index 69f081a2..de376dd2 100644 --- a/client/client.pro +++ b/client/client.pro @@ -39,6 +39,7 @@ HEADERS += \ managementserver.h \ protocols/protocols_defs.h \ settings.h \ + ui/notificationhandler.h \ ui/models/containers_model.h \ ui/models/protocols_model.h \ ui/pages.h \ @@ -63,6 +64,7 @@ HEADERS += \ ui/pages_logic/protocols/ShadowSocksLogic.h \ ui/property_helper.h \ ui/models/servers_model.h \ + ui/systemtray_notificationhandler.h \ ui/uilogic.h \ ui/qautostart.h \ ui/models/sites_model.h \ @@ -90,6 +92,7 @@ SOURCES += \ managementserver.cpp \ protocols/protocols_defs.cpp \ settings.cpp \ + ui/notificationhandler.cpp \ ui/models/containers_model.cpp \ ui/models/protocols_model.cpp \ ui/pages_logic/AppSettingsLogic.cpp \ @@ -112,6 +115,7 @@ SOURCES += \ ui/pages_logic/protocols/PageProtocolLogicBase.cpp \ ui/pages_logic/protocols/ShadowSocksLogic.cpp \ ui/models/servers_model.cpp \ + ui/systemtray_notificationhandler.cpp \ ui/uilogic.cpp \ ui/qautostart.cpp \ ui/models/sites_model.cpp \ @@ -199,9 +203,16 @@ android { INCLUDEPATH += platforms/android - HEADERS += protocols/android_vpnprotocol.h \ + HEADERS += \ + platforms/android/android_controller.h \ + platforms/android/android_notificationhandler.h \ + protocols/android_vpnprotocol.h + + SOURCES += \ + platforms/android/android_controller.cpp \ + platforms/android/android_notificationhandler.cpp \ + protocols/android_vpnprotocol.cpp - SOURCES += protocols/android_vpnprotocol.cpp \ DISTFILES += \ android/AndroidManifest.xml \ diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index 9375cd1d..06479316 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -1,6 +1,7 @@ #include "servercontroller.h" #include +#include #include #include #include @@ -10,6 +11,7 @@ #include #include #include +#include #include "sftpchannel.h" #include "sshconnectionmanager.h" @@ -134,12 +136,7 @@ ErrorCode ServerController::runContainerScript(const ServerCredentials &credenti { QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh"; - QString mkdir = "sudo docker exec -i $CONTAINER_NAME mkdir -p /opt/amnezia/"; - ErrorCode e = runScript(credentials, - replaceVars(mkdir, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); - if (e) return e; - - e = uploadTextFileToContainer(container, credentials, script, fileName); + ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName); if (e) return e; QString runner = QString("sudo docker exec -i $CONTAINER_NAME bash %1 ").arg(fileName); @@ -167,6 +164,14 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, stdOut += data + "\n"; }; + // mkdir + QFileInfo fi(path); + QString mkdir = "sudo docker exec -i $CONTAINER_NAME mkdir -p " + fi.absoluteDir().absolutePath(); + e = runScript(credentials, + replaceVars(mkdir, genVarsForScript(credentials, container))); + if (e) return e; + + if (overwriteMode == QSsh::SftpOverwriteMode::SftpOverwriteExisting) { e = runScript(credentials, replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path), diff --git a/client/images/icon_src.svg b/client/images/icon_src.svg new file mode 100644 index 00000000..b27d1360 --- /dev/null +++ b/client/images/icon_src.svg @@ -0,0 +1,67 @@ + + + + + + + + diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp new file mode 100644 index 00000000..6b2c1aa0 --- /dev/null +++ b/client/platforms/android/android_controller.cpp @@ -0,0 +1,334 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "android_controller.h" +#include "core/errorstrings.h" + +// Binder Codes for VPNServiceBinder +// See also - VPNServiceBinder.kt +// Actions that are Requestable +const int ACTION_ACTIVATE = 1; +const int ACTION_DEACTIVATE = 2; +const int ACTION_REGISTERLISTENER = 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; + +// 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; + +namespace { +AndroidController* s_instance = nullptr; + +constexpr auto PERMISSIONHELPER_CLASS = + "org/amnezia/vpn/qt/VPNPermissionHelper"; + +} // namespace + +AndroidController::AndroidController(): + m_binder(this) +{ + +} + +AndroidController* AndroidController::instance() { + if (!s_instance) s_instance = new AndroidController(); + return s_instance; +} + +bool AndroidController::initialize() +{ + qDebug() << "Initializing"; + + // Hook in the native implementation for startActivityForResult into the JNI + JNINativeMethod methods[]{{"startActivityForResult", + "(Landroid/content/Intent;)V", + reinterpret_cast(startActivityForResult)}}; + QAndroidJniObject javaClass(PERMISSIONHELPER_CLASS); + QAndroidJniEnvironment env; + jclass objectClass = env->GetObjectClass(javaClass.object()); + env->RegisterNatives(objectClass, methods, + sizeof(methods) / sizeof(methods[0])); + env->DeleteLocalRef(objectClass); + + auto appContext = QtAndroid::androidActivity().callObjectMethod( + "getApplicationContext", "()Landroid/content/Context;"); + + QAndroidJniObject::callStaticMethod( + "org/amnezia/vpn/VPNService", "startService", + "(Landroid/content/Context;)V", appContext.object()); + + // Start the VPN Service (if not yet) and Bind to it + const bool bindResult = QtAndroid::bindService( + QAndroidIntent(appContext.object(), "org.amnezia.vpn.VPNService"), + *this, QtAndroid::BindFlag::AutoCreate); + qDebug() << "Binding to the service..." << bindResult; + + return bindResult; +} + +ErrorCode AndroidController::start() +{ + + //qDebug().noquote() << "AndroidController::start" << QJsonDocument(m_rawConfig).toJson(); + qDebug() << "Prompting for VPN permission"; + auto appContext = QtAndroid::androidActivity().callObjectMethod( + "getApplicationContext", "()Landroid/content/Context;"); + QAndroidJniObject::callStaticMethod( + PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V", + appContext.object()); + + QAndroidParcel sendData; + sendData.writeData(QJsonDocument(m_vpnConfig).toJson()); + bool activateResult = false; + while (!activateResult){ + activateResult = m_serviceBinder.transact(ACTION_ACTIVATE, sendData, nullptr); + } + + return activateResult ? NoError : UnknownError; +} + +void AndroidController::stop() { + qDebug() << "AndroidController::stop"; + +// if (reason != ReasonNone) { +// // Just show that we're disconnected +// // we're doing the actual disconnect once +// // the vpn-service has the new server ready in Action->Activate +// emit disconnected(); +// qCritical() << "deactivation skipped for Switching"; +// return; +// } + + QAndroidParcel nullData; + m_serviceBinder.transact(ACTION_DEACTIVATE, nullData, nullptr); +} + +// Activates the tunnel that is currently set +// in the VPN Service +void AndroidController::resumeStart() { + QAndroidParcel nullData; + m_serviceBinder.transact(ACTION_RESUME_ACTIVATE, nullData, nullptr); +} + +/* + * Sets the current notification text that is shown + */ +void AndroidController::setNotificationText(const QString& title, + const QString& message, + int timerSec) { + QJsonObject args; + args["title"] = title; + args["message"] = message; + args["sec"] = timerSec; + QJsonDocument doc(args); + QAndroidParcel data; + data.writeData(doc.toJson()); + m_serviceBinder.transact(ACTION_SET_NOTIFICATION_TEXT, data, nullptr); +} + +/* + * Sets fallback Notification text that should be shown in case the VPN + * switches into the Connected state without the app open + * e.g via always-on vpn + */ +void AndroidController::setFallbackConnectedNotification() { + QJsonObject args; + args["title"] = tr("AmneziaVPN"); + //% "Ready for you to connect" + //: Refers to the app - which is currently running the background and waiting + args["message"] = tr("VPN Connected"); + QJsonDocument doc(args); + QAndroidParcel data; + data.writeData(doc.toJson()); + m_serviceBinder.transact(ACTION_SET_NOTIFICATION_FALLBACK, data, nullptr); +} + +void AndroidController::checkStatus() { + qDebug() << "check status"; + + QAndroidParcel nullParcel; + m_serviceBinder.transact(ACTION_REQUEST_STATISTIC, nullParcel, nullptr); +} + +void AndroidController::getBackendLogs(std::function&& a_callback) { + qDebug() << "get logs"; + + m_logCallback = std::move(a_callback); + QAndroidParcel nullData, replyData; + m_serviceBinder.transact(ACTION_REQUEST_GET_LOG, nullData, &replyData); +} + +void AndroidController::cleanupBackendLogs() { + qDebug() << "cleanup logs"; + + QAndroidParcel nullParcel; + m_serviceBinder.transact(ACTION_REQUEST_CLEANUP_LOG, nullParcel, nullptr); +} + +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_REGISTERLISTENER, binderParcel, nullptr); +} + +void AndroidController::onServiceDisconnected(const QString& name) { + qDebug() << "Server disconnected"; + m_serviceConnected = false; + Q_UNUSED(name); + // TODO: Maybe restart? Or crash? +} + +const QJsonObject &AndroidController::vpnConfig() const +{ + return m_vpnConfig; +} + +void AndroidController::setVpnConfig(const QJsonObject &newVpnConfig) +{ + m_vpnConfig = newVpnConfig; +} + + +/** + * @brief AndroidController::VPNBinder::onTransact + * @param code the Event-Type we get From the VPNService See + * @param data - Might contain UTF-8 JSON in case the Event has a payload + * @param reply - always null + * @param flags - unused + * @return Returns true is the code was a valid Event Code + */ +bool AndroidController::VPNBinder::onTransact(int code, + const QAndroidParcel& data, + const QAndroidParcel& reply, + QAndroidBinder::CallType flags) { + Q_UNUSED(data); + Q_UNUSED(reply); + Q_UNUSED(flags); + + QJsonDocument doc; + QString buffer; + switch (code) { + case EVENT_INIT: + qDebug() << "Transact: init"; + doc = QJsonDocument::fromJson(data.readData()); + emit m_controller->initialized( + true, doc.object()["connected"].toBool(), + QDateTime::fromMSecsSinceEpoch( + doc.object()["time"].toVariant().toLongLong())); + // Pass a localised version of the Fallback string for the Notification + m_controller->setFallbackConnectedNotification(); + + break; + case EVENT_CONNECTED: + qDebug() << "Transact: connected"; + emit m_controller->connectionStateChanged(VpnProtocol::Connected); + break; + case EVENT_DISCONNECTED: + qDebug() << "Transact: disconnected"; + emit m_controller->connectionStateChanged(VpnProtocol::Disconnected); + break; + case EVENT_STATISTIC_UPDATE: + qDebug() << "Transact:: update"; + + // Data is here a JSON String + doc = QJsonDocument::fromJson(data.readData()); + // TODO update counters +// emit m_controller->statusUpdated(doc.object()["endpoint"].toString(), +// doc.object()["deviceIpv4"].toString(), +// doc.object()["totalTX"].toInt(), +// doc.object()["totalRX"].toInt()); + break; + case EVENT_BACKEND_LOGS: + qDebug() << "Transact: backend logs"; + + buffer = readUTF8Parcel(data); + if (m_controller->m_logCallback) { + m_controller->m_logCallback(buffer); + } + break; + case EVENT_ACTIVATION_ERROR: + qDebug() << "Transact: error"; + emit m_controller->connectionStateChanged(VpnProtocol::Error); + + 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; +/** + * @brief Starts the Given intent in Context of the QTActivity + * @param env + * @param intent + */ +void AndroidController::startActivityForResult(JNIEnv *env, jobject, jobject intent) +{ + qDebug() << "start activity"; + Q_UNUSED(env); + QtAndroid::startActivity(intent, 1337, + [](int receiverRequestCode, int resultCode, + const QAndroidJniObject& data) { + // Currently this function just used in + // VPNService.kt::checkPersmissions. So the result + // we're getting is if the User gave us the + // Vpn.bind permission. In case of NO we should + // abort. + Q_UNUSED(receiverRequestCode); + Q_UNUSED(data); + + AndroidController* controller = + AndroidController::instance(); + if (!controller) { + return; + } + + if (resultCode == ACTIVITY_RESULT_OK) { + qDebug() << "VPN PROMPT RESULT - Accepted"; + controller->resumeStart(); + return; + } + // If the request got rejected abort the current + // connection. + qWarning() << "VPN PROMPT RESULT - Rejected"; + emit controller->connectionStateChanged(VpnProtocol::Disconnected); + }); + return; +} diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h new file mode 100644 index 00000000..97b088c7 --- /dev/null +++ b/client/platforms/android/android_controller.h @@ -0,0 +1,84 @@ +#ifndef ANDROID_CONTROLLER_H +#define ANDROID_CONTROLLER_H + +#include +#include + +#include "protocols/vpnprotocol.h" +using namespace amnezia; + + + +class AndroidController : public QObject, public QAndroidServiceConnection +{ + Q_OBJECT + +public: + explicit AndroidController(); + static AndroidController* instance(); + + virtual ~AndroidController() override = default; + + bool initialize(); + + ErrorCode start(); + void stop(); + void resumeStart(); + + void checkStatus(); + void setNotificationText(const QString& title, const QString& message, int timerSec); + void setFallbackConnectedNotification(); + void getBackendLogs(std::function&& callback); + void cleanupBackendLogs(); + + // from QAndroidServiceConnection + void onServiceConnected(const QString& name, const QAndroidBinder& serviceBinder) override; + void onServiceDisconnected(const QString& name) override; + + const QJsonObject &vpnConfig() const; + void setVpnConfig(const QJsonObject &newVpnConfig); + +signals: + void connectionStateChanged(VpnProtocol::ConnectionState state); + + // This signal is emitted when the controller is initialized. Note that the + // VPN tunnel can be already active. In this case, "connected" should be set + // to true and the "connectionDate" should be set to the activation date if + // known. + // If "status" is set to false, the backend service is considered unavailable. + void initialized(bool status, bool connected, + const QDateTime& connectionDate); + +protected slots: + +protected: + + +private: + //Protocol m_protocol; + QJsonObject m_vpnConfig; + + bool m_serviceConnected = false; + std::function m_logCallback; + + QAndroidBinder m_serviceBinder; + class VPNBinder : public QAndroidBinder { + public: + VPNBinder(AndroidController* controller) : m_controller(controller) {} + + bool onTransact(int code, const QAndroidParcel& data, + const QAndroidParcel& reply, + QAndroidBinder::CallType flags) override; + + QString readUTF8Parcel(QAndroidParcel data); + + private: + AndroidController* m_controller = nullptr; + }; + + VPNBinder m_binder; + + static void startActivityForResult(JNIEnv* env, jobject /*thiz*/, jobject intent); +}; + +#endif // ANDROID_CONTROLLER_H diff --git a/client/platforms/android/android_notificationhandler.cpp b/client/platforms/android/android_notificationhandler.cpp new file mode 100644 index 00000000..2bd26b7f --- /dev/null +++ b/client/platforms/android/android_notificationhandler.cpp @@ -0,0 +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/. */ + +#include "android_notificationhandler.h" +#include "platforms/android/android_controller.h" + +AndroidNotificationHandler::AndroidNotificationHandler(QObject* parent) + : NotificationHandler(parent) { +} +AndroidNotificationHandler::~AndroidNotificationHandler() { +} + +void AndroidNotificationHandler::notify(NotificationHandler::Message type, + const QString& title, + const QString& message, int timerMsec) { + Q_UNUSED(type); + qDebug() << "Send notification - " << message; + AndroidController::instance()->setNotificationText(title, message, + timerMsec / 1000); +} diff --git a/client/platforms/android/android_notificationhandler.h b/client/platforms/android/android_notificationhandler.h new file mode 100644 index 00000000..e3e7325e --- /dev/null +++ b/client/platforms/android/android_notificationhandler.h @@ -0,0 +1,24 @@ +/* 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 ANDROIDNOTIFICATIONHANDLER_H +#define ANDROIDNOTIFICATIONHANDLER_H + +#include "ui/notificationhandler.h" + +#include + +class AndroidNotificationHandler final : public NotificationHandler { + Q_DISABLE_COPY_MOVE(AndroidNotificationHandler) + + public: + AndroidNotificationHandler(QObject* parent); + ~AndroidNotificationHandler(); + + protected: + void notify(Message type, const QString& title, const QString& message, + int timerMsec) override; +}; + +#endif // ANDROIDNOTIFICATIONHANDLER_H diff --git a/client/protocols/android_vpnprotocol.cpp b/client/protocols/android_vpnprotocol.cpp index 0b986ab5..41818dd5 100644 --- a/client/protocols/android_vpnprotocol.cpp +++ b/client/protocols/android_vpnprotocol.cpp @@ -17,338 +17,25 @@ #include "android_vpnprotocol.h" #include "core/errorstrings.h" -// Binder Codes for VPNServiceBinder -// See also - VPNServiceBinder.kt -// Actions that are Requestable -const int ACTION_ACTIVATE = 1; -const int ACTION_DEACTIVATE = 2; -const int ACTION_REGISTERLISTENER = 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; +#include "platforms/android/android_controller.h" -// 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; - -namespace { -AndroidVpnProtocol* s_instance = nullptr; - -constexpr auto PERMISSIONHELPER_CLASS = - "org/amnezia/vpn/qt/VPNPermissionHelper"; - -} // namespace AndroidVpnProtocol::AndroidVpnProtocol(Protocol protocol, const QJsonObject &configuration, QObject* parent) : VpnProtocol(configuration, parent), - m_protocol(protocol), - m_binder(this) + m_protocol(protocol) { } -AndroidVpnProtocol* AndroidVpnProtocol::instance() { - return s_instance; -} - -bool AndroidVpnProtocol::initialize() -{ - qDebug() << "Initializing"; - - // Hook in the native implementation for startActivityForResult into the JNI - JNINativeMethod methods[]{{"startActivityForResult", - "(Landroid/content/Intent;)V", - reinterpret_cast(startActivityForResult)}}; - QAndroidJniObject javaClass(PERMISSIONHELPER_CLASS); - QAndroidJniEnvironment env; - jclass objectClass = env->GetObjectClass(javaClass.object()); - env->RegisterNatives(objectClass, methods, - sizeof(methods) / sizeof(methods[0])); - env->DeleteLocalRef(objectClass); - - auto appContext = QtAndroid::androidActivity().callObjectMethod( - "getApplicationContext", "()Landroid/content/Context;"); - - QAndroidJniObject::callStaticMethod( - "org/amnezia/vpn/VPNService", "startService", - "(Landroid/content/Context;)V", appContext.object()); - - // Start the VPN Service (if not yet) and Bind to it - const bool bindResult = QtAndroid::bindService( - QAndroidIntent(appContext.object(), "org.amnezia.vpn.VPNService"), - *this, QtAndroid::BindFlag::AutoCreate); - qDebug() << "Binding to the service..." << bindResult; - - return bindResult; -} - ErrorCode AndroidVpnProtocol::start() { - - //qDebug().noquote() << "AndroidVpnProtocol::start" << QJsonDocument(m_rawConfig).toJson(); - qDebug() << "Prompting for VPN permission"; - auto appContext = QtAndroid::androidActivity().callObjectMethod( - "getApplicationContext", "()Landroid/content/Context;"); - QAndroidJniObject::callStaticMethod( - PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V", - appContext.object()); - - - // QJsonObject jServer; - // jServer["ipv4AddrIn"] = server.ipv4AddrIn(); - // jServer["ipv4Gateway"] = server.ipv4Gateway(); - // jServer["ipv6AddrIn"] = server.ipv6AddrIn(); - // jServer["ipv6Gateway"] = server.ipv6Gateway(); - // jServer["publicKey"] = server.publicKey(); - // jServer["port"] = (int)server.choosePort(); - - // QJsonArray allowedIPs; - // foreach (auto item, allowedIPAddressRanges) { - // QJsonValue val; - // val = item.toString(); - // allowedIPs.append(val); - // } - - // QJsonArray excludedApps; - // foreach (auto appID, vpnDisabledApps) { - // excludedApps.append(QJsonValue(appID)); - // } - - // QJsonObject args; - // args["device"] = jDevice; - // args["keys"] = jKeys; - // args["server"] = jServer; - // args["reason"] = (int)reason; - // args["allowedIPs"] = allowedIPs; - // args["excludedApps"] = excludedApps; - // args["dns"] = dns.toString(); - - QAndroidParcel sendData; - sendData.writeData(QJsonDocument(m_rawConfig).toJson()); - bool activateResult = false; - while (!activateResult){ - activateResult = m_serviceBinder.transact(ACTION_ACTIVATE, sendData, nullptr); - } - - return activateResult ? NoError : UnknownError; + AndroidController::instance()->setVpnConfig(m_rawConfig); + return AndroidController::instance()->start(); } -// Activates the tunnel that is currently set -// in the VPN Service -void AndroidVpnProtocol::resume_start() { - QAndroidParcel nullData; - m_serviceBinder.transact(ACTION_RESUME_ACTIVATE, nullData, nullptr); -} - -void AndroidVpnProtocol::stop() { - qDebug() << "deactivation"; - -// if (reason != ReasonNone) { -// // Just show that we're disconnected -// // we're doing the actual disconnect once -// // the vpn-service has the new server ready in Action->Activate -// emit disconnected(); -// logger.warning() << "deactivation skipped for Switching"; -// return; -// } - - QAndroidParcel nullData; - m_serviceBinder.transact(ACTION_DEACTIVATE, nullData, nullptr); -} - -/* - * Sets the current notification text that is shown - */ -void AndroidVpnProtocol::setNotificationText(const QString& title, - const QString& message, - int timerSec) { - QJsonObject args; - args["title"] = title; - args["message"] = message; - args["sec"] = timerSec; - QJsonDocument doc(args); - QAndroidParcel data; - data.writeData(doc.toJson()); - m_serviceBinder.transact(ACTION_SET_NOTIFICATION_TEXT, data, nullptr); -} - -/* - * Sets fallback Notification text that should be shown in case the VPN - * switches into the Connected state without the app open - * e.g via always-on vpn - */ -void AndroidVpnProtocol::setFallbackConnectedNotification() { - QJsonObject args; - args["title"] = qtTrId("vpn.main.productName"); - //% "Ready for you to connect" - //: Refers to the app - which is currently running the background and waiting - args["message"] = qtTrId("vpn.android.notification.isIDLE"); - QJsonDocument doc(args); - QAndroidParcel data; - data.writeData(doc.toJson()); - m_serviceBinder.transact(ACTION_SET_NOTIFICATION_FALLBACK, data, nullptr); -} - -void AndroidVpnProtocol::checkStatus() { - qDebug() << "check status"; - - QAndroidParcel nullParcel; - m_serviceBinder.transact(ACTION_REQUEST_STATISTIC, nullParcel, nullptr); -} - -void AndroidVpnProtocol::getBackendLogs(std::function&& a_callback) { - qDebug() << "get logs"; - - m_logCallback = std::move(a_callback); - QAndroidParcel nullData, replyData; - m_serviceBinder.transact(ACTION_REQUEST_GET_LOG, nullData, &replyData); -} - -void AndroidVpnProtocol::cleanupBackendLogs() { - qDebug() << "cleanup logs"; - - QAndroidParcel nullParcel; - m_serviceBinder.transact(ACTION_REQUEST_CLEANUP_LOG, nullParcel, nullptr); -} - -void AndroidVpnProtocol::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_REGISTERLISTENER, binderParcel, nullptr); -} - -void AndroidVpnProtocol::onServiceDisconnected(const QString& name) { - qDebug() << "Server disconnected"; - m_serviceConnected = false; - Q_UNUSED(name); - // TODO: Maybe restart? Or crash? -} - - -/** - * @brief AndroidController::VPNBinder::onTransact - * @param code the Event-Type we get From the VPNService See - * @param data - Might contain UTF-8 JSON in case the Event has a payload - * @param reply - always null - * @param flags - unused - * @return Returns true is the code was a valid Event Code - */ -bool AndroidVpnProtocol::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"; - m_controller->setConnectionState(Connected); - break; - case EVENT_DISCONNECTED: - qDebug() << "Transact: disconnected"; - m_controller->setConnectionState(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: - m_controller->setConnectionState(Error); - default: - qWarning() << "Transact: Invalid!"; - break; - } - - return true; -} - -QString AndroidVpnProtocol::VPNBinder::readUTF8Parcel(QAndroidParcel data) { - // 106 is the Code for UTF-8 - return QTextCodec::codecForMib(106)->toUnicode(data.readData()); -} - -const int ACTIVITY_RESULT_OK = 0xffffffff; -/** - * @brief Starts the Given intent in Context of the QTActivity - * @param env - * @param intent - */ -void AndroidVpnProtocol::startActivityForResult(JNIEnv *env, jobject, jobject intent) +void AndroidVpnProtocol::stop() { - qDebug() << "start activity"; - 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); - - AndroidVpnProtocol* controller = - AndroidVpnProtocol::instance(); - if (!controller) { - return; - } - - if (resultCode == ACTIVITY_RESULT_OK) { - qDebug() << "VPN PROMPT RESULT - Accepted"; - controller->resume_start(); - return; - } - // If the request got rejected abort the current - // connection. - qWarning() << "VPN PROMPT RESULT - Rejected"; - controller->setConnectionState(Disconnected); - - }); - return; + qDebug() << "AndroidVpnProtocol::stop()"; + AndroidController::instance()->stop(); } + diff --git a/client/protocols/android_vpnprotocol.h b/client/protocols/android_vpnprotocol.h index 23365eee..ede6ffd8 100644 --- a/client/protocols/android_vpnprotocol.h +++ b/client/protocols/android_vpnprotocol.h @@ -11,38 +11,16 @@ using namespace amnezia; -class AndroidVpnProtocol : public VpnProtocol, - public QAndroidServiceConnection +class AndroidVpnProtocol : public VpnProtocol { Q_OBJECT public: explicit AndroidVpnProtocol(Protocol protocol, const QJsonObject& configuration, QObject* parent = nullptr); - static AndroidVpnProtocol* instance(); - virtual ~AndroidVpnProtocol() override = default; - bool initialize(); - - virtual ErrorCode start() override; - virtual void stop() override; - - void resume_start(); - - void checkStatus(); - - void setNotificationText(const QString& title, const QString& message, - int timerSec); - void setFallbackConnectedNotification(); - - void getBackendLogs(std::function&& callback); - - void cleanupBackendLogs(); - - // from QAndroidServiceConnection - void onServiceConnected(const QString& name, - const QAndroidBinder& serviceBinder) override; - void onServiceDisconnected(const QString& name) override; + ErrorCode start() override; + void stop() override; signals: @@ -55,28 +33,6 @@ protected: private: Protocol m_protocol; - bool m_serviceConnected = false; - std::function m_logCallback; - - QAndroidBinder m_serviceBinder; - class VPNBinder : public QAndroidBinder { - public: - VPNBinder(AndroidVpnProtocol* controller) : m_controller(controller) {} - - bool onTransact(int code, const QAndroidParcel& data, - const QAndroidParcel& reply, - QAndroidBinder::CallType flags) override; - - QString readUTF8Parcel(QAndroidParcel data); - - private: - AndroidVpnProtocol* m_controller = nullptr; - }; - - VPNBinder m_binder; - - static void startActivityForResult(JNIEnv* env, jobject /*thiz*/, - jobject intent); }; #endif // ANDROID_VPNPROTOCOL_H diff --git a/client/protocols/vpnprotocol.h b/client/protocols/vpnprotocol.h index c6bfc3b2..6db5e29e 100644 --- a/client/protocols/vpnprotocol.h +++ b/client/protocols/vpnprotocol.h @@ -48,23 +48,16 @@ signals: void timeoutTimerEvent(); void protocolError(amnezia::ErrorCode e); - // This signal is emitted when the controller is initialized. Note that the - // VPN tunnel can be already active. In this case, "connected" should be set - // to true and the "connectionDate" should be set to the activation date if - // known. - // If "status" is set to false, the backend service is considered unavailable. - void initialized(bool status, bool connected, - const QDateTime& connectionDate); -protected slots: - virtual void onTimeout(); +public slots: + virtual void onTimeout(); // todo: remove? + + void setBytesChanged(quint64 receivedBytes, quint64 sentBytes); + void setConnectionState(VpnProtocol::ConnectionState state); protected: void startTimeoutTimer(); void stopTimeoutTimer(); - virtual void setBytesChanged(quint64 receivedBytes, quint64 sentBytes); - virtual void setConnectionState(VpnProtocol::ConnectionState state); - ConnectionState m_connectionState; QString m_routeGateway; QString m_vpnLocalAddress; diff --git a/client/ui/notificationhandler.cpp b/client/ui/notificationhandler.cpp new file mode 100644 index 00000000..7a2387a9 --- /dev/null +++ b/client/ui/notificationhandler.cpp @@ -0,0 +1,124 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include "notificationhandler.h" + +#if defined(Q_OS_IOS) +# include "platforms/ios/iosnotificationhandler.h" +#elif defined(Q_OS_ANDROID) +# include "platforms/android/android_notificationhandler.h" +#else + +# if defined(Q_OS_LINUX) +# include "platforms/linux/linuxsystemtraynotificationhandler.h" +# endif + +# include "systemtraynotificationhandler.h" +#endif + +// static +NotificationHandler* NotificationHandler::create(QObject* parent) { +#if defined(Q_OS_IOS) + return new IOSNotificationHandler(parent); +#elif defined(Q_OS_ANDROID) + return new AndroidNotificationHandler(parent); +#else + +# if defined(Q_OS_LINUX) + if (LinuxSystemTrayNotificationHandler::requiredCustomImpl()) { + return new LinuxSystemTrayNotificationHandler(parent); + } +# endif + + return new SystemTrayNotificationHandler(parent); +#endif +} + +namespace { +NotificationHandler* s_instance = nullptr; +} // namespace + +// static +NotificationHandler* NotificationHandler::instance() { + Q_ASSERT(s_instance); + return s_instance; +} + +NotificationHandler::NotificationHandler(QObject* parent) : QObject(parent) { + Q_ASSERT(!s_instance); + s_instance = this; +} + +NotificationHandler::~NotificationHandler() { + Q_ASSERT(s_instance == this); + s_instance = nullptr; +} + +void NotificationHandler::showVpnStateNotification(VpnProtocol::ConnectionState state) +{ + if (state != VpnProtocol::Connected && state != VpnProtocol::Disconnected) { + return; + } + + QString title; + QString message; + + switch (state) { + case VpnProtocol::ConnectionState::Connected: + m_connected = true; + + title = tr("AmneziaVPN"); + message = tr("VPN Connected"); + break; + + case VpnProtocol::ConnectionState::Disconnected: + if (m_connected) { + m_connected = false; + title = tr("AmneziaVPN"); + message = tr("VPN Disconnected"); + } + break; + + default: + break; + } + + Q_ASSERT(title.isEmpty() == message.isEmpty()); + + if (!title.isEmpty()) { + notifyInternal(VpnState, title, message, 2000); + } +} + +void NotificationHandler::unsecuredNetworkNotification(const QString& networkName) { + qDebug() << "Unsecured network notification shown"; + + + QString title = tr("AmneziaVPN notification"); + QString message = tr("Unsucured network detected: ") + networkName; + + notifyInternal(UnsecuredNetwork, title, message, 2000); +} + +void NotificationHandler::notifyInternal(Message type, const QString& title, + const QString& message, + int timerMsec) { + m_lastMessage = type; + + emit notificationShown(title, message); + notify(type, title, message, timerMsec); +} + +void NotificationHandler::messageClickHandle() { + qDebug() << "Message clicked"; + + if (m_lastMessage == VpnState) { + qCritical() << "Random message clicked received"; + return; + } + + emit notificationClicked(m_lastMessage); + m_lastMessage = VpnState; +} diff --git a/client/ui/notificationhandler.h b/client/ui/notificationhandler.h new file mode 100644 index 00000000..67ab46cf --- /dev/null +++ b/client/ui/notificationhandler.h @@ -0,0 +1,61 @@ +/* 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 NOTIFICATIONHANDLER_H +#define NOTIFICATIONHANDLER_H + +#include +#include "protocols/vpnprotocol.h" + +class QMenu; + +class NotificationHandler : public QObject { + Q_OBJECT + Q_DISABLE_COPY_MOVE(NotificationHandler) + +public: + enum Message { + VpnState, + UnsecuredNetwork + }; + + static NotificationHandler* create(QObject* parent); + + static NotificationHandler* instance(); + + virtual ~NotificationHandler(); + + void unsecuredNetworkNotification(const QString& networkName); + + void messageClickHandle(); + +public slots: + void showVpnStateNotification(VpnProtocol::ConnectionState state); + +signals: + void notificationShown(const QString& title, const QString& message); + + void notificationClicked(Message message); + +protected: + explicit NotificationHandler(QObject* parent); + + virtual void notify(Message type, const QString& title, + const QString& message, int timerMsec) = 0; + +private: + virtual void notifyInternal(Message type, const QString& title, + const QString& message, int timerMsec); + +protected: + Message m_lastMessage = VpnState; + +private: + + // We want to show a 'disconnected' notification only if we were actually + // connected. + bool m_connected = false; +}; + +#endif // NOTIFICATIONHANDLER_H diff --git a/client/ui/systemtray_notificationhandler.cpp b/client/ui/systemtray_notificationhandler.cpp new file mode 100644 index 00000000..25d0253b --- /dev/null +++ b/client/ui/systemtray_notificationhandler.cpp @@ -0,0 +1,127 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include "systemtray_notificationhandler.h" + + +#ifdef MVPN_MACOS +# include "platforms/macos/macosutils.h" +#endif + +#include +#include + + +SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) + : NotificationHandler(parent) { + +// MozillaVPN* vpn = MozillaVPN::instance(); +// Q_ASSERT(vpn); + +// connect(vpn, &MozillaVPN::stateChanged, this, +// &SystemTrayNotificationHandler::updateContextMenu); + +// connect(vpn->currentServer(), &ServerData::changed, this, +// &SystemTrayNotificationHandler::updateContextMenu); + +// connect(vpn->controller(), &Controller::stateChanged, this, +// &SystemTrayNotificationHandler::updateContextMenu); + +// connect(vpn->statusIcon(), &StatusIcon::iconChanged, this, +// &SystemTrayNotificationHandler::updateIcon); + +// m_systemTrayIcon.setToolTip(qtTrId("vpn.main.productName")); + +// // Status label +// m_statusLabel = m_menu.addAction(""); +// m_statusLabel->setEnabled(false); + +// m_lastLocationLabel = +// m_menu.addAction("", vpn->controller(), &Controller::activate); +// m_lastLocationLabel->setEnabled(false); + +// m_disconnectAction = +// m_menu.addAction("", vpn->controller(), &Controller::deactivate); + +// m_separator = m_menu.addSeparator(); + +// m_showHideLabel = m_menu.addAction( +// "", this, &SystemTrayNotificationHandler::showHideWindow); + +// m_menu.addSeparator(); + +// m_helpMenu = m_menu.addMenu(""); + +// m_preferencesAction = m_menu.addAction("", vpn, &MozillaVPN::requestSettings); + +// m_menu.addSeparator(); + +// m_quitAction = m_menu.addAction("", vpn->controller(), &Controller::quit); +// m_systemTrayIcon.setContextMenu(&m_menu); + +// updateIcon(vpn->statusIcon()->iconString()); + +// connect(QmlEngineHolder::instance()->window(), &QWindow::visibleChanged, this, +// &SystemTrayNotificationHandler::updateContextMenu); + +// connect(&m_systemTrayIcon, &QSystemTrayIcon::activated, this, +// &SystemTrayNotificationHandler::maybeActivated); + +// connect(&m_systemTrayIcon, &QSystemTrayIcon::messageClicked, this, +// &SystemTrayNotificationHandler::messageClickHandle); + +// retranslate(); + +// m_systemTrayIcon.show(); +} + +SystemTrayNotificationHandler::~SystemTrayNotificationHandler() { +} + +void SystemTrayNotificationHandler::notify(NotificationHandler::Message type, + const QString& title, + const QString& message, + int timerMsec) { + Q_UNUSED(type); + +// QIcon icon(Constants::LOGO_URL); +// m_systemTrayIcon.showMessage(title, message, icon, timerMsec); +} + +void SystemTrayNotificationHandler::updateIcon(const QString& icon) { + QIcon trayIconMask(icon); + trayIconMask.setIsMask(true); + m_systemTrayIcon.setIcon(trayIconMask); +} + +void SystemTrayNotificationHandler::showHideWindow() { +// QmlEngineHolder* engine = QmlEngineHolder::instance(); +// if (engine->window()->isVisible()) { +// engine->hideWindow(); +//#ifdef MVPN_MACOS +// MacOSUtils::hideDockIcon(); +//#endif +// } else { +// engine->showWindow(); +//#ifdef MVPN_MACOS +// MacOSUtils::showDockIcon(); +//#endif +// } +} + +void SystemTrayNotificationHandler::maybeActivated( + QSystemTrayIcon::ActivationReason reason) { + qDebug() << "Activated"; + +#if defined(MVPN_WINDOWS) || defined(MVPN_LINUX) + if (reason == QSystemTrayIcon::DoubleClick || + reason == QSystemTrayIcon::Trigger) { + QmlEngineHolder* engine = QmlEngineHolder::instance(); + engine->showWindow(); + } +#else + Q_UNUSED(reason); +#endif +} diff --git a/client/ui/systemtray_notificationhandler.h b/client/ui/systemtray_notificationhandler.h new file mode 100644 index 00000000..1bd228de --- /dev/null +++ b/client/ui/systemtray_notificationhandler.h @@ -0,0 +1,45 @@ +/* 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 SYSTEMTRAY_NOTIFICATIONHANDLER_H +#define SYSTEMTRAY_NOTIFICATIONHANDLER_H + +#include "notificationhandler.h" + +#include +#include + +class SystemTrayNotificationHandler : public NotificationHandler { + public: + explicit SystemTrayNotificationHandler(QObject* parent); + ~SystemTrayNotificationHandler(); + + protected: + virtual void notify(Message type, const QString& title, + const QString& message, int timerMsec) override; + + private: + void updateIcon(const QString& icon); + + void updateContextMenu(); + + void showHideWindow(); + + void maybeActivated(QSystemTrayIcon::ActivationReason reason); + + private: + QMenu m_menu; + QSystemTrayIcon m_systemTrayIcon; + + QAction* m_statusLabel = nullptr; + QAction* m_lastLocationLabel = nullptr; + QAction* m_disconnectAction = nullptr; + QAction* m_separator = nullptr; + QAction* m_preferencesAction = nullptr; + QAction* m_showHideLabel = nullptr; + QAction* m_quitAction = nullptr; + QMenu* m_helpMenu = nullptr; +}; + +#endif // SYSTEMTRAY_NOTIFICATIONHANDLER_H diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index fc08c392..d4c13d71 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -44,6 +44,10 @@ #include "ui/macos_util.h" #endif +#ifdef Q_OS_ANDROID +#include "platforms/android/android_controller.h" +#endif + #include "pages_logic/AppSettingsLogic.h" #include "pages_logic/GeneralSettingsLogic.h" #include "pages_logic/NetworkSettingsLogic.h" @@ -128,9 +132,18 @@ UiLogic::~UiLogic() void UiLogic::initalizeUiLogic() { + if (!AndroidController::instance()->initialize()) { + qDebug() << QString("Init failed") ; + emit VpnProtocol::Error; + return; + } + qDebug() << "UiLogic::initalizeUiLogic()"; setupTray(); + notificationHandler = NotificationHandler::create(this); + + connect(m_vpnConnection, &VpnConnection::connectionStateChanged, notificationHandler, &NotificationHandler::showVpnStateNotification); // if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::Windows7) { // needToHideCustomTitlebar = true; diff --git a/client/ui/uilogic.h b/client/ui/uilogic.h index 3a33ea45..21c77681 100644 --- a/client/ui/uilogic.h +++ b/client/ui/uilogic.h @@ -16,6 +16,7 @@ #include "models/containers_model.h" #include "models/protocols_model.h" +#include "notificationhandler.h" #include "settings.h" class AppSettingsLogic; @@ -218,6 +219,8 @@ private: QThread m_vpnConnectionThread; Settings m_settings; + NotificationHandler* notificationHandler; + // QRegExpValidator m_ipAddressValidator; // QRegExpValidator m_ipAddressPortValidator; diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index bd913dbb..f6225874 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -260,11 +261,8 @@ void VpnConnection::connectToVpn(int serverIndex, #else Protocol proto = ContainerProps::defaultProtocol(container); AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration); - if (!androidVpnProtocol->initialize()) { - qDebug() << QString("Init failed") ; - emit VpnProtocol::Error; - return; - } + connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, &AndroidVpnProtocol::setConnectionState); + m_vpnProtocol.reset(androidVpnProtocol); #endif