Android qt 6.7 (#1024)
* Up Gradle to 8.10 * Update Android dependencies * Up Qt to 6.7.2 * Up qtkeychain to 0.14.3 * Move function of changing the color of the navigation bar to the android side * Fix splashscreen and recent apps thumbnail backgrounds * Android authentication refactoring * Fix GitHub action * Fix the extra circle around the connect button on Android * Fix keyboard popup * Increase the amount of requestNetwork attempts on Android 11
This commit is contained in:
parent
cd70b7e619
commit
175477d31f
33 changed files with 256 additions and 192 deletions
14
.github/workflows/deploy.yml
vendored
14
.github/workflows/deploy.yml
vendored
|
|
@ -297,24 +297,24 @@ jobs:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
ANDROID_BUILD_PLATFORM: android-34
|
ANDROID_BUILD_PLATFORM: android-34
|
||||||
QT_VERSION: 6.6.2
|
QT_VERSION: 6.7.2
|
||||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install desktop Qt'
|
- name: 'Install desktop Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'linux'
|
host: 'linux'
|
||||||
target: 'desktop'
|
target: 'desktop'
|
||||||
arch: 'gcc_64'
|
arch: 'linux_gcc_64'
|
||||||
modules: ${{ env.QT_MODULES }}
|
modules: ${{ env.QT_MODULES }}
|
||||||
dir: ${{ runner.temp }}
|
dir: ${{ runner.temp }}
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Install android_x86_64 Qt'
|
- name: 'Install android_x86_64 Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'linux'
|
host: 'linux'
|
||||||
|
|
@ -325,7 +325,7 @@ jobs:
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Install android_x86 Qt'
|
- name: 'Install android_x86 Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'linux'
|
host: 'linux'
|
||||||
|
|
@ -336,7 +336,7 @@ jobs:
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Install android_armv7 Qt'
|
- name: 'Install android_armv7 Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'linux'
|
host: 'linux'
|
||||||
|
|
@ -347,7 +347,7 @@ jobs:
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Install android_arm64_v8a Qt'
|
- name: 'Install android_arm64_v8a Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'linux'
|
host: 'linux'
|
||||||
|
|
|
||||||
2
client/3rd/qtkeychain
vendored
2
client/3rd/qtkeychain
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 74776e2a3e2d98d19943e0968901c5b5e04cc1bd
|
Subproject commit 7460df6a978669290de5b56c2d98b199b61c3f88
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
<manifest
|
<manifest
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="org.amnezia.vpn"
|
|
||||||
android:versionName="-- %%INSERT_VERSION_NAME%% --"
|
android:versionName="-- %%INSERT_VERSION_NAME%% --"
|
||||||
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
|
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
|
||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
|
|
@ -46,7 +45,7 @@
|
||||||
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|
||||||
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
|
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="stateUnchanged|adjustResize"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
@ -68,9 +67,6 @@
|
||||||
android:name="android.app.lib_name"
|
android:name="android.app.lib_name"
|
||||||
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
|
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="android.app.extract_android_style"
|
|
||||||
android:value="minimal" />
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
|
@ -88,6 +84,13 @@
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/Translucent" />
|
android:theme="@style/Translucent" />
|
||||||
|
|
||||||
|
<activity android:name=".AuthActivity"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/Translucent" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ImportConfigActivity"
|
android:name=".ImportConfigActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,6 @@
|
||||||
// android.bundle.enableUncompressedNativeLibs is deprecated
|
// android.bundle.enableUncompressedNativeLibs is deprecated
|
||||||
// disable adding gradle property android.bundle.enableUncompressedNativeLibs by androiddeployqt
|
// disable adding gradle property android.bundle.enableUncompressedNativeLibs by androiddeployqt
|
||||||
useLegacyPackaging
|
useLegacyPackaging
|
||||||
|
|
||||||
|
// package name for androiddeployqt
|
||||||
|
namespace = "org.amnezia.vpn"
|
||||||
|
|
|
||||||
|
|
@ -115,9 +115,11 @@ dependencies {
|
||||||
implementation(project(":xray"))
|
implementation(project(":xray"))
|
||||||
implementation(libs.androidx.core)
|
implementation(libs.androidx.core)
|
||||||
implementation(libs.androidx.activity)
|
implementation(libs.androidx.activity)
|
||||||
|
implementation(libs.androidx.fragment)
|
||||||
implementation(libs.kotlinx.coroutines)
|
implementation(libs.kotlinx.coroutines)
|
||||||
implementation(libs.kotlinx.serialization.protobuf)
|
implementation(libs.kotlinx.serialization.protobuf)
|
||||||
implementation(libs.bundles.androidx.camera)
|
implementation(libs.bundles.androidx.camera)
|
||||||
implementation(libs.google.mlkit)
|
implementation(libs.google.mlkit)
|
||||||
implementation(libs.androidx.datastore)
|
implementation(libs.androidx.datastore)
|
||||||
|
implementation(libs.androidx.biometric)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,28 @@
|
||||||
[versions]
|
[versions]
|
||||||
agp = "8.2.0"
|
agp = "8.5.2"
|
||||||
kotlin = "1.9.20"
|
kotlin = "1.9.24"
|
||||||
androidx-core = "1.12.0"
|
androidx-core = "1.13.1"
|
||||||
androidx-activity = "1.8.1"
|
androidx-activity = "1.9.1"
|
||||||
androidx-annotation = "1.7.0"
|
androidx-annotation = "1.8.2"
|
||||||
androidx-camera = "1.3.0"
|
androidx-biometric = "1.2.0-alpha05"
|
||||||
|
androidx-camera = "1.3.4"
|
||||||
|
androidx-fragment = "1.8.2"
|
||||||
androidx-security-crypto = "1.1.0-alpha06"
|
androidx-security-crypto = "1.1.0-alpha06"
|
||||||
androidx-datastore = "1.1.0-beta01"
|
androidx-datastore = "1.1.1"
|
||||||
kotlinx-coroutines = "1.7.3"
|
kotlinx-coroutines = "1.8.1"
|
||||||
kotlinx-serialization = "1.6.3"
|
kotlinx-serialization = "1.6.3"
|
||||||
google-mlkit = "17.2.0"
|
google-mlkit = "17.3.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
|
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
|
||||||
androidx-activity = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" }
|
androidx-activity = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" }
|
||||||
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
|
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
|
||||||
|
androidx-biometric = { module = "androidx.biometric:biometric-ktx", version.ref = "androidx-biometric" }
|
||||||
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "androidx-camera" }
|
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "androidx-camera" }
|
||||||
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" }
|
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" }
|
||||||
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" }
|
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" }
|
||||||
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" }
|
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" }
|
||||||
|
androidx-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment" }
|
||||||
androidx-security-crypto = { module = "androidx.security:security-crypto-ktx", version.ref = "androidx-security-crypto" }
|
androidx-security-crypto = { module = "androidx.security:security-crypto-ktx", version.ref = "androidx-security-crypto" }
|
||||||
androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
|
androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
|
||||||
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
|
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
|
||||||
|
|
|
||||||
BIN
client/android/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
client/android/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
|
|
@ -1,7 +1,5 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
||||||
networkTimeout=10000
|
|
||||||
validateDistributionUrl=true
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
||||||
7
client/android/gradlew
vendored
7
client/android/gradlew
vendored
|
|
@ -15,6 +15,8 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
|
|
@ -55,7 +57,7 @@
|
||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
|
@ -84,7 +86,8 @@ done
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||||
|
' "$PWD" ) || exit
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
|
|
|
||||||
22
client/android/gradlew.bat
vendored
22
client/android/gradlew.bat
vendored
|
|
@ -13,6 +13,8 @@
|
||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|
@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
|
@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,5 +21,5 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar"))))
|
api(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar"))))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<color name="black">#FF0E0E11</color>
|
||||||
<style name="NoActionBar">
|
<style name="NoActionBar">
|
||||||
|
<item name="android:windowBackground">@color/black</item>
|
||||||
|
<item name="android:colorBackground">@color/black</item>
|
||||||
<item name="android:windowActionBar">false</item>
|
<item name="android:windowActionBar">false</item>
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ dependencyResolutionManagement {
|
||||||
includeBuild("./gradle/plugins")
|
includeBuild("./gradle/plugins")
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.settings") version "8.2.0"
|
id("com.android.settings") version "8.5.2"
|
||||||
id("settings-property-delegate")
|
id("settings-property-delegate")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,10 @@ class AmneziaActivity : QtActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Log.d(TAG, "Create Amnezia activity: $intent")
|
Log.d(TAG, "Create Amnezia activity: $intent")
|
||||||
|
window.apply {
|
||||||
|
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||||
|
statusBarColor = getColor(R.color.black)
|
||||||
|
}
|
||||||
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||||
val proto = mainScope.async(Dispatchers.IO) {
|
val proto = mainScope.async(Dispatchers.IO) {
|
||||||
VpnStateStore.getVpnState().vpnProto
|
VpnStateStore.getVpnState().vpnProto
|
||||||
|
|
@ -610,6 +614,14 @@ class AmneziaActivity : QtActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun setNavigationBarColor(color: Int) {
|
||||||
|
Log.v(TAG, "Change navigation bar color: ${"#%08X".format(color)}")
|
||||||
|
mainScope.launch {
|
||||||
|
window.navigationBarColor = color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun minimizeApp() {
|
fun minimizeApp() {
|
||||||
Log.v(TAG, "Minimize application")
|
Log.v(TAG, "Minimize application")
|
||||||
|
|
@ -684,6 +696,17 @@ class AmneziaActivity : QtActivity() {
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun requestAuthentication() {
|
||||||
|
Log.v(TAG, "Request authentication")
|
||||||
|
mainScope.launch {
|
||||||
|
qtInitialized.await()
|
||||||
|
Intent(this@AmneziaActivity, AuthActivity::class.java).also {
|
||||||
|
startActivity(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utils methods
|
* Utils methods
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
97
client/android/src/org/amnezia/vpn/AuthActivity.kt
Normal file
97
client/android/src/org/amnezia/vpn/AuthActivity.kt
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
package org.amnezia.vpn
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.biometric.BiometricManager
|
||||||
|
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
|
||||||
|
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
|
import androidx.biometric.BiometricPrompt.AuthenticationResult
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import org.amnezia.vpn.qt.QtAndroidController
|
||||||
|
import org.amnezia.vpn.util.Log
|
||||||
|
|
||||||
|
private const val TAG = "AuthActivity"
|
||||||
|
|
||||||
|
private const val AUTHENTICATORS = BIOMETRIC_STRONG or DEVICE_CREDENTIAL
|
||||||
|
|
||||||
|
class AuthActivity : FragmentActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val biometricManager = BiometricManager.from(applicationContext)
|
||||||
|
when (biometricManager.canAuthenticate(AUTHENTICATORS)) {
|
||||||
|
BiometricManager.BIOMETRIC_SUCCESS -> {
|
||||||
|
showBiometricPrompt(biometricManager)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> {
|
||||||
|
Log.w(TAG, "Unknown biometric status")
|
||||||
|
showBiometricPrompt(biometricManager)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> {
|
||||||
|
Log.e(TAG, "The specified options are incompatible with the current Android " +
|
||||||
|
"version ${Build.VERSION.SDK_INT}")
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> {
|
||||||
|
Log.w(TAG, "The hardware is unavailable")
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
|
||||||
|
Log.w(TAG, "No biometric or device credential is enrolled")
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> {
|
||||||
|
Log.w(TAG, "There is no suitable hardware")
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> {
|
||||||
|
Log.w(TAG, "A security vulnerability has been discovered with one or " +
|
||||||
|
"more hardware sensors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QtAndroidController.onAuthResult(true)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showBiometricPrompt(biometricManager: BiometricManager) {
|
||||||
|
val executor = ContextCompat.getMainExecutor(applicationContext)
|
||||||
|
val biometricPrompt = BiometricPrompt(this, executor,
|
||||||
|
object : BiometricPrompt.AuthenticationCallback() {
|
||||||
|
override fun onAuthenticationSucceeded(result: AuthenticationResult) {
|
||||||
|
super.onAuthenticationSucceeded(result)
|
||||||
|
Log.d(TAG, "Authentication succeeded")
|
||||||
|
QtAndroidController.onAuthResult(true)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationFailed() {
|
||||||
|
super.onAuthenticationFailed()
|
||||||
|
Log.w(TAG, "Authentication failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||||
|
super.onAuthenticationError(errorCode, errString)
|
||||||
|
Log.e(TAG, "Authentication error $errorCode: $errString")
|
||||||
|
QtAndroidController.onAuthResult(false)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
val promptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||||
|
.setAllowedAuthenticators(AUTHENTICATORS)
|
||||||
|
.setTitle("AmneziaVPN")
|
||||||
|
.setSubtitle(biometricManager.getStrings(AUTHENTICATORS)?.promptMessage)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
biometricPrompt.authenticate(promptInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package org.amnezia.vpn;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.app.KeyguardManager;
|
|
||||||
import android.content.Intent;
|
|
||||||
import org.qtproject.qt.android.bindings.QtActivity;
|
|
||||||
|
|
||||||
|
|
||||||
import static android.content.Context.KEYGUARD_SERVICE;
|
|
||||||
|
|
||||||
public class AuthHelper extends QtActivity {
|
|
||||||
|
|
||||||
static final String TAG = "AuthHelper";
|
|
||||||
|
|
||||||
public static Intent getAuthIntent(Context context) {
|
|
||||||
KeyguardManager mKeyguardManager = (KeyguardManager)context.getSystemService(KEYGUARD_SERVICE);
|
|
||||||
if (mKeyguardManager.isDeviceSecure()) {
|
|
||||||
return mKeyguardManager.createConfirmDeviceCredentialIntent(null, null);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -33,10 +33,10 @@ class ImportConfigActivity : ComponentActivity() {
|
||||||
intent?.let(::readConfig)
|
intent?.let(::readConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent?) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
Log.d(TAG, "onNewIntent: $intent")
|
Log.d(TAG, "onNewIntent: $intent")
|
||||||
intent?.let(::readConfig)
|
intent.let(::readConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readConfig(intent: Intent) {
|
private fun readConfig(intent: Intent) {
|
||||||
|
|
|
||||||
|
|
@ -25,5 +25,7 @@ object QtAndroidController {
|
||||||
|
|
||||||
external fun onConfigImported(data: String)
|
external fun onConfigImported(data: String)
|
||||||
|
|
||||||
|
external fun onAuthResult(result: Boolean)
|
||||||
|
|
||||||
external fun decodeQrCode(data: String): Boolean
|
external fun decodeQrCode(data: String): Boolean
|
||||||
}
|
}
|
||||||
|
|
@ -88,7 +88,7 @@ class NetworkState(
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
|
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
|
||||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val numberAttempts = 3
|
val numberAttempts = 300
|
||||||
var attemptCount = 0
|
var attemptCount = 0
|
||||||
while(true) {
|
while(true) {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
|
||||||
set(HEADERS ${HEADERS}
|
set(HEADERS ${HEADERS}
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
|
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.h
|
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.h
|
||||||
)
|
)
|
||||||
|
|
@ -35,7 +34,6 @@ set(HEADERS ${HEADERS}
|
||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.cpp
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@ bool AndroidController::initialize()
|
||||||
{"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)},
|
{"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)},
|
||||||
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
|
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
|
||||||
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
|
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
|
||||||
|
{"onAuthResult", "(Z)V", reinterpret_cast<void *>(onAuthResult)},
|
||||||
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)}
|
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -210,6 +211,11 @@ void AndroidController::setScreenshotsEnabled(bool enabled)
|
||||||
callActivityMethod("setScreenshotsEnabled", "(Z)V", enabled);
|
callActivityMethod("setScreenshotsEnabled", "(Z)V", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AndroidController::setNavigationBarColor(unsigned int color)
|
||||||
|
{
|
||||||
|
callActivityMethod("setNavigationBarColor", "(I)V", color);
|
||||||
|
}
|
||||||
|
|
||||||
void AndroidController::minimizeApp()
|
void AndroidController::minimizeApp()
|
||||||
{
|
{
|
||||||
callActivityMethod("minimizeApp", "()V");
|
callActivityMethod("minimizeApp", "()V");
|
||||||
|
|
@ -265,6 +271,22 @@ void AndroidController::requestNotificationPermission()
|
||||||
callActivityMethod("requestNotificationPermission", "()V");
|
callActivityMethod("requestNotificationPermission", "()V");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AndroidController::requestAuthentication()
|
||||||
|
{
|
||||||
|
QEventLoop wait;
|
||||||
|
bool result;
|
||||||
|
connect(this, &AndroidController::authenticationResult, this,
|
||||||
|
[&result, &wait](const bool &authResult){
|
||||||
|
qDebug() << "Android authentication result:" << authResult;
|
||||||
|
result = authResult;
|
||||||
|
wait.quit();
|
||||||
|
},
|
||||||
|
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
|
||||||
|
callActivityMethod("requestAuthentication", "()V");
|
||||||
|
wait.exec();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// Moving log processing to the Android side
|
// Moving log processing to the Android side
|
||||||
jclass AndroidController::log;
|
jclass AndroidController::log;
|
||||||
jmethodID AndroidController::logDebug;
|
jmethodID AndroidController::logDebug;
|
||||||
|
|
@ -462,6 +484,14 @@ void AndroidController::onConfigImported(JNIEnv *env, jobject thiz, jstring data
|
||||||
emit AndroidController::instance()->configImported(AndroidUtils::convertJString(env, data));
|
emit AndroidController::instance()->configImported(AndroidUtils::convertJString(env, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
void AndroidController::onAuthResult(JNIEnv *env, jobject thiz, jboolean result)
|
||||||
|
{
|
||||||
|
Q_UNUSED(thiz);
|
||||||
|
|
||||||
|
emit AndroidController::instance()->authenticationResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data)
|
bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -41,11 +41,13 @@ public:
|
||||||
void exportLogsFile(const QString &fileName);
|
void exportLogsFile(const QString &fileName);
|
||||||
void clearLogs();
|
void clearLogs();
|
||||||
void setScreenshotsEnabled(bool enabled);
|
void setScreenshotsEnabled(bool enabled);
|
||||||
|
void setNavigationBarColor(unsigned int color);
|
||||||
void minimizeApp();
|
void minimizeApp();
|
||||||
QJsonArray getAppList();
|
QJsonArray getAppList();
|
||||||
QPixmap getAppIcon(const QString &package, QSize *size, const QSize &requestedSize);
|
QPixmap getAppIcon(const QString &package, QSize *size, const QSize &requestedSize);
|
||||||
bool isNotificationPermissionGranted();
|
bool isNotificationPermissionGranted();
|
||||||
void requestNotificationPermission();
|
void requestNotificationPermission();
|
||||||
|
bool requestAuthentication();
|
||||||
|
|
||||||
static bool initLogging();
|
static bool initLogging();
|
||||||
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
|
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
|
||||||
|
|
@ -63,6 +65,7 @@ signals:
|
||||||
void configImported(QString config);
|
void configImported(QString config);
|
||||||
void importConfigFromOutside(QString config);
|
void importConfigFromOutside(QString config);
|
||||||
void initConnectionState(Vpn::ConnectionState state);
|
void initConnectionState(Vpn::ConnectionState state);
|
||||||
|
void authenticationResult(bool result);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isWaitingStatus = true;
|
bool isWaitingStatus = true;
|
||||||
|
|
@ -89,6 +92,7 @@ private:
|
||||||
static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
|
static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
|
||||||
static void onConfigImported(JNIEnv *env, jobject thiz, jstring data);
|
static void onConfigImported(JNIEnv *env, jobject thiz, jstring data);
|
||||||
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
|
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
|
||||||
|
static void onAuthResult(JNIEnv *env, jobject thiz, jboolean result);
|
||||||
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
|
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
|
||||||
|
|
||||||
template <typename Ret, typename ...Args>
|
template <typename Ret, typename ...Args>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#include "authResultReceiver.h"
|
|
||||||
|
|
||||||
AuthResultReceiver::AuthResultReceiver(QSharedPointer<AuthResultNotifier> ¬ifier) : m_notifier(notifier)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data)
|
|
||||||
{
|
|
||||||
qDebug() << "receiverRequestCode" << receiverRequestCode << "resultCode" << resultCode;
|
|
||||||
|
|
||||||
if (resultCode == -1) { // ResultOK
|
|
||||||
emit m_notifier->authSuccessful();
|
|
||||||
} else {
|
|
||||||
emit m_notifier->authFailed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
#ifndef AUTHRESULTRECEIVER_H
|
|
||||||
#define AUTHRESULTRECEIVER_H
|
|
||||||
|
|
||||||
#include <QJniObject>
|
|
||||||
|
|
||||||
#include <private/qandroidextras_p.h>
|
|
||||||
|
|
||||||
class AuthResultNotifier : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
AuthResultNotifier(QObject *parent = nullptr) : QObject(parent) {};
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void authFailed();
|
|
||||||
void authSuccessful();
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Auth result handler for Android */
|
|
||||||
class AuthResultReceiver final : public QAndroidActivityResultReceiver
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AuthResultReceiver(QSharedPointer<AuthResultNotifier> ¬ifier);
|
|
||||||
|
|
||||||
void handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QSharedPointer<AuthResultNotifier> m_notifier;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // AUTHRESULTRECEIVER_H
|
|
||||||
|
|
@ -10,9 +10,6 @@
|
||||||
|
|
||||||
#include "core/controllers/vpnConfigurationController.h"
|
#include "core/controllers/vpnConfigurationController.h"
|
||||||
#include "systemController.h"
|
#include "systemController.h"
|
||||||
#ifdef Q_OS_ANDROID
|
|
||||||
#include "platforms/android/android_utils.h"
|
|
||||||
#endif
|
|
||||||
#include "qrcodegen.hpp"
|
#include "qrcodegen.hpp"
|
||||||
|
|
||||||
ExportController::ExportController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ContainersModel> &containersModel,
|
ExportController::ExportController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ContainersModel> &containersModel,
|
||||||
|
|
@ -24,12 +21,6 @@ ExportController::ExportController(const QSharedPointer<ServersModel> &serversMo
|
||||||
m_clientManagementModel(clientManagementModel),
|
m_clientManagementModel(clientManagementModel),
|
||||||
m_settings(settings)
|
m_settings(settings)
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_ANDROID
|
|
||||||
m_authResultNotifier.reset(new AuthResultNotifier);
|
|
||||||
m_authResultReceiver.reset(new AuthResultReceiver(m_authResultNotifier));
|
|
||||||
connect(m_authResultNotifier.get(), &AuthResultNotifier::authFailed, this, [this]() { emit exportErrorOccurred(tr("Access error!")); });
|
|
||||||
connect(m_authResultNotifier.get(), &AuthResultNotifier::authSuccessful, this, &ExportController::generateFullAccessConfig);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExportController::generateFullAccessConfig()
|
void ExportController::generateFullAccessConfig()
|
||||||
|
|
@ -63,26 +54,6 @@ void ExportController::generateFullAccessConfig()
|
||||||
emit exportConfigChanged();
|
emit exportConfigChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID)
|
|
||||||
void ExportController::generateFullAccessConfigAndroid()
|
|
||||||
{
|
|
||||||
/* We use builtin keyguard for ssh key export protection on Android */
|
|
||||||
QJniObject activity = AndroidUtils::getActivity();
|
|
||||||
auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;");
|
|
||||||
if (appContext.isValid()) {
|
|
||||||
auto intent = QJniObject::callStaticObjectMethod("org/amnezia/vpn/AuthHelper", "getAuthIntent",
|
|
||||||
"(Landroid/content/Context;)Landroid/content/Intent;", appContext.object());
|
|
||||||
if (intent.isValid()) {
|
|
||||||
if (intent.object<jobject>() != nullptr) {
|
|
||||||
QtAndroidPrivate::startActivity(intent.object<jobject>(), 1, m_authResultReceiver.get());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
generateFullAccessConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void ExportController::generateConnectionConfig(const QString &clientName)
|
void ExportController::generateConnectionConfig(const QString &clientName)
|
||||||
{
|
{
|
||||||
clearPreviousConfig();
|
clearPreviousConfig();
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,6 @@
|
||||||
#include "ui/models/clientManagementModel.h"
|
#include "ui/models/clientManagementModel.h"
|
||||||
#include "ui/models/containers_model.h"
|
#include "ui/models/containers_model.h"
|
||||||
#include "ui/models/servers_model.h"
|
#include "ui/models/servers_model.h"
|
||||||
#ifdef Q_OS_ANDROID
|
|
||||||
#include "platforms/android/authResultReceiver.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class ExportController : public QObject
|
class ExportController : public QObject
|
||||||
{
|
{
|
||||||
|
|
@ -25,9 +22,6 @@ public:
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void generateFullAccessConfig();
|
void generateFullAccessConfig();
|
||||||
#if defined(Q_OS_ANDROID)
|
|
||||||
void generateFullAccessConfigAndroid();
|
|
||||||
#endif
|
|
||||||
void generateConnectionConfig(const QString &clientName);
|
void generateConnectionConfig(const QString &clientName);
|
||||||
void generateOpenVpnConfig(const QString &clientName);
|
void generateOpenVpnConfig(const QString &clientName);
|
||||||
void generateWireGuardConfig(const QString &clientName);
|
void generateWireGuardConfig(const QString &clientName);
|
||||||
|
|
@ -74,11 +68,6 @@ private:
|
||||||
QString m_config;
|
QString m_config;
|
||||||
QString m_nativeConfigString;
|
QString m_nativeConfigString;
|
||||||
QList<QString> m_qrCodes;
|
QList<QString> m_qrCodes;
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
|
||||||
QSharedPointer<AuthResultNotifier> m_authResultNotifier;
|
|
||||||
QSharedPointer<QAndroidActivityResultReceiver> m_authResultReceiver;
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // EXPORTCONTROLLER_H
|
#endif // EXPORTCONTROLLER_H
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
#include "platforms/android/android_controller.h"
|
#include "platforms/android/android_controller.h"
|
||||||
#include "platforms/android/android_utils.h"
|
|
||||||
#include <QJniObject>
|
|
||||||
#endif
|
#endif
|
||||||
#if defined Q_OS_MAC
|
#if defined Q_OS_MAC
|
||||||
#include "ui/macos_util.h"
|
#include "ui/macos_util.h"
|
||||||
|
|
@ -22,18 +20,8 @@ PageController::PageController(const QSharedPointer<ServersModel> &serversModel,
|
||||||
: QObject(parent), m_serversModel(serversModel), m_settings(settings)
|
: QObject(parent), m_serversModel(serversModel), m_settings(settings)
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
// Change color of navigation and status bar's
|
|
||||||
auto initialPageNavigationBarColor = getInitialPageNavigationBarColor();
|
auto initialPageNavigationBarColor = getInitialPageNavigationBarColor();
|
||||||
AndroidUtils::runOnAndroidThreadSync([&initialPageNavigationBarColor]() {
|
AndroidController::instance()->setNavigationBarColor(initialPageNavigationBarColor);
|
||||||
QJniObject activity = AndroidUtils::getActivity();
|
|
||||||
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
|
|
||||||
if (window.isValid()) {
|
|
||||||
window.callMethod<void>("addFlags", "(I)V", 0x80000000);
|
|
||||||
window.callMethod<void>("clearFlags", "(I)V", 0x04000000);
|
|
||||||
window.callMethod<void>("setStatusBarColor", "(I)V", 0xFF0E0E11);
|
|
||||||
window.callMethod<void>("setNavigationBarColor", "(I)V", initialPageNavigationBarColor);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined Q_OS_MACX
|
#if defined Q_OS_MACX
|
||||||
|
|
@ -115,14 +103,7 @@ unsigned int PageController::getInitialPageNavigationBarColor()
|
||||||
void PageController::updateNavigationBarColor(const int color)
|
void PageController::updateNavigationBarColor(const int color)
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
// Change color of navigation bar
|
AndroidController::instance()->setNavigationBarColor(color);
|
||||||
AndroidUtils::runOnAndroidThreadSync([&color]() {
|
|
||||||
QJniObject activity = AndroidUtils::getActivity();
|
|
||||||
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
|
|
||||||
if (window.isValid()) {
|
|
||||||
window.callMethod<void>("setNavigationBarColor", "(I)V", color);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -285,3 +285,12 @@ QString SettingsController::getGatewayEndpoint()
|
||||||
{
|
{
|
||||||
return m_settings->getGatewayEndpoint();
|
return m_settings->getGatewayEndpoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SettingsController::isOnTv()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_ANDROID
|
||||||
|
return AndroidController::instance()->isOnTv();
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
@ -82,6 +82,8 @@ public slots:
|
||||||
void setGatewayEndpoint(const QString &endpoint);
|
void setGatewayEndpoint(const QString &endpoint);
|
||||||
QString getGatewayEndpoint();
|
QString getGatewayEndpoint();
|
||||||
|
|
||||||
|
bool isOnTv();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void primaryDnsChanged();
|
void primaryDnsChanged();
|
||||||
void secondaryDnsChanged();
|
void secondaryDnsChanged();
|
||||||
|
|
|
||||||
|
|
@ -125,3 +125,12 @@ void SystemController::setQmlRoot(QObject *qmlRoot)
|
||||||
{
|
{
|
||||||
m_qmlRoot = qmlRoot;
|
m_qmlRoot = qmlRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SystemController::isAuthenticated()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_ANDROID
|
||||||
|
return AndroidController::instance()->requestAuthentication();
|
||||||
|
#else
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ public slots:
|
||||||
|
|
||||||
void setQmlRoot(QObject *qmlRoot);
|
void setQmlRoot(QObject *qmlRoot);
|
||||||
|
|
||||||
|
bool isAuthenticated();
|
||||||
signals:
|
signals:
|
||||||
void fileDialogClosed(const bool isAccepted);
|
void fileDialogClosed(const bool isAccepted);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ Button {
|
||||||
property string defaultButtonColor: AmneziaStyle.color.paleGray
|
property string defaultButtonColor: AmneziaStyle.color.paleGray
|
||||||
property string progressButtonColor: AmneziaStyle.color.paleGray
|
property string progressButtonColor: AmneziaStyle.color.paleGray
|
||||||
property string connectedButtonColor: AmneziaStyle.color.goldenApricot
|
property string connectedButtonColor: AmneziaStyle.color.goldenApricot
|
||||||
|
property bool buttonActiveFocus: activeFocus && (Qt.platform.os !== "android" || SettingsController.isOnTv())
|
||||||
|
|
||||||
implicitWidth: 190
|
implicitWidth: 190
|
||||||
implicitHeight: 190
|
implicitHeight: 190
|
||||||
|
|
@ -50,14 +51,14 @@ Button {
|
||||||
verticalOffset: 0
|
verticalOffset: 0
|
||||||
radius: 10
|
radius: 10
|
||||||
samples: 25
|
samples: 25
|
||||||
color: root.activeFocus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.goldenApricot
|
color: root.buttonActiveFocus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.goldenApricot
|
||||||
source: backgroundCircle
|
source: backgroundCircle
|
||||||
}
|
}
|
||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
fillColor: AmneziaStyle.color.transparent
|
fillColor: AmneziaStyle.color.transparent
|
||||||
strokeColor: AmneziaStyle.color.paleGray
|
strokeColor: AmneziaStyle.color.paleGray
|
||||||
strokeWidth: root.activeFocus ? 1 : 0
|
strokeWidth: root.buttonActiveFocus ? 1 : 0
|
||||||
capStyle: ShapePath.RoundCap
|
capStyle: ShapePath.RoundCap
|
||||||
|
|
||||||
PathAngleArc {
|
PathAngleArc {
|
||||||
|
|
@ -81,14 +82,14 @@ Button {
|
||||||
return defaultButtonColor
|
return defaultButtonColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
strokeWidth: root.activeFocus ? 2 : 3
|
strokeWidth: root.buttonActiveFocus ? 2 : 3
|
||||||
capStyle: ShapePath.RoundCap
|
capStyle: ShapePath.RoundCap
|
||||||
|
|
||||||
PathAngleArc {
|
PathAngleArc {
|
||||||
centerX: backgroundCircle.width / 2
|
centerX: backgroundCircle.width / 2
|
||||||
centerY: backgroundCircle.height / 2
|
centerY: backgroundCircle.height / 2
|
||||||
radiusX: 93 - (root.activeFocus ? 2 : 0)
|
radiusX: 93 - (root.buttonActiveFocus ? 2 : 0)
|
||||||
radiusY: 93 - (root.activeFocus ? 2 : 0)
|
radiusY: 93 - (root.buttonActiveFocus ? 2 : 0)
|
||||||
startAngle: 0
|
startAngle: 0
|
||||||
sweepAngle: 360
|
sweepAngle: 360
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -140,22 +140,23 @@ PageType {
|
||||||
Keys.onTabPressed: lastItemTabClicked(focusItem)
|
Keys.onTabPressed: lastItemTabClicked(focusItem)
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
|
PageController.showBusyIndicator(true)
|
||||||
|
|
||||||
|
if (Qt.platform.os === "android" && !SystemController.isAuthenticated()) {
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
ExportController.exportErrorOccurred(qsTr("Access error!"))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
ExportController.generateFullAccessConfig()
|
||||||
|
}
|
||||||
|
|
||||||
shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text
|
shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text
|
||||||
shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text
|
shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text
|
||||||
|
|
||||||
shareConnectionDrawer.open()
|
shareConnectionDrawer.open()
|
||||||
shareConnectionDrawer.contentVisible = false
|
shareConnectionDrawer.contentVisible = true
|
||||||
PageController.showBusyIndicator(true)
|
|
||||||
|
|
||||||
if (Qt.platform.os === "android") {
|
|
||||||
ExportController.generateFullAccessConfigAndroid();
|
|
||||||
} else {
|
|
||||||
ExportController.generateFullAccessConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
PageController.showBusyIndicator(false)
|
PageController.showBusyIndicator(false)
|
||||||
|
|
||||||
shareConnectionDrawer.contentVisible = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue