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:
albexk 2024-09-09 14:36:33 +03:00 committed by GitHub
parent cd70b7e619
commit 175477d31f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 256 additions and 192 deletions

View file

@ -297,24 +297,24 @@ jobs:
env:
ANDROID_BUILD_PLATFORM: android-34
QT_VERSION: 6.6.2
QT_VERSION: 6.7.2
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
steps:
- name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: 'linux'
target: 'desktop'
arch: 'gcc_64'
arch: 'linux_gcc_64'
modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }}
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Install android_x86_64 Qt'
uses: jurplel/install-qt-action@v3
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: 'linux'
@ -325,7 +325,7 @@ jobs:
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Install android_x86 Qt'
uses: jurplel/install-qt-action@v3
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: 'linux'
@ -336,7 +336,7 @@ jobs:
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Install android_armv7 Qt'
uses: jurplel/install-qt-action@v3
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: 'linux'
@ -347,7 +347,7 @@ jobs:
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Install android_arm64_v8a Qt'
uses: jurplel/install-qt-action@v3
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: 'linux'

@ -1 +1 @@
Subproject commit 74776e2a3e2d98d19943e0968901c5b5e04cc1bd
Subproject commit 7460df6a978669290de5b56c2d98b199b61c3f88

View file

@ -3,7 +3,6 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.amnezia.vpn"
android:versionName="-- %%INSERT_VERSION_NAME%% --"
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
android:installLocation="auto">
@ -46,7 +45,7 @@
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
android:launchMode="singleInstance"
android:windowSoftInputMode="adjustResize"
android:windowSoftInputMode="stateUnchanged|adjustResize"
android:exported="true">
<intent-filter>
@ -68,9 +67,6 @@
android:name="android.app.lib_name"
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
<meta-data
android:name="android.app.extract_android_style"
android:value="minimal" />
</activity>
<activity
@ -88,6 +84,13 @@
android:exported="false"
android:theme="@style/Translucent" />
<activity android:name=".AuthActivity"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:taskAffinity=""
android:exported="false"
android:theme="@style/Translucent" />
<activity
android:name=".ImportConfigActivity"
android:excludeFromRecents="true"

View file

@ -3,3 +3,6 @@
// android.bundle.enableUncompressedNativeLibs is deprecated
// disable adding gradle property android.bundle.enableUncompressedNativeLibs by androiddeployqt
useLegacyPackaging
// package name for androiddeployqt
namespace = "org.amnezia.vpn"

View file

@ -115,9 +115,11 @@ dependencies {
implementation(project(":xray"))
implementation(libs.androidx.core)
implementation(libs.androidx.activity)
implementation(libs.androidx.fragment)
implementation(libs.kotlinx.coroutines)
implementation(libs.kotlinx.serialization.protobuf)
implementation(libs.bundles.androidx.camera)
implementation(libs.google.mlkit)
implementation(libs.androidx.datastore)
implementation(libs.androidx.biometric)
}

View file

@ -1,24 +1,28 @@
[versions]
agp = "8.2.0"
kotlin = "1.9.20"
androidx-core = "1.12.0"
androidx-activity = "1.8.1"
androidx-annotation = "1.7.0"
androidx-camera = "1.3.0"
agp = "8.5.2"
kotlin = "1.9.24"
androidx-core = "1.13.1"
androidx-activity = "1.9.1"
androidx-annotation = "1.8.2"
androidx-biometric = "1.2.0-alpha05"
androidx-camera = "1.3.4"
androidx-fragment = "1.8.2"
androidx-security-crypto = "1.1.0-alpha06"
androidx-datastore = "1.1.0-beta01"
kotlinx-coroutines = "1.7.3"
androidx-datastore = "1.1.1"
kotlinx-coroutines = "1.8.1"
kotlinx-serialization = "1.6.3"
google-mlkit = "17.2.0"
google-mlkit = "17.3.0"
[libraries]
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
androidx-activity = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" }
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-camera2 = { module = "androidx.camera:camera-camera2", 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-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-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }

Binary file not shown.

View file

@ -1,7 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (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.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# 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.
MAX_FD=maximum

View file

@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail

View file

@ -21,5 +21,5 @@ android {
}
dependencies {
implementation(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar"))))
api(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar"))))
}

View file

@ -1,6 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF0E0E11</color>
<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:windowNoTitle">true</item>
</style>

View file

@ -22,7 +22,7 @@ dependencyResolutionManagement {
includeBuild("./gradle/plugins")
plugins {
id("com.android.settings") version "8.2.0"
id("com.android.settings") version "8.5.2"
id("settings-property-delegate")
}

View file

@ -158,6 +158,10 @@ class AmneziaActivity : QtActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
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)
val proto = mainScope.async(Dispatchers.IO) {
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")
fun minimizeApp() {
Log.v(TAG, "Minimize application")
@ -684,6 +696,17 @@ class AmneziaActivity : QtActivity() {
.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
*/

View 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)
}
}

View file

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

View file

@ -33,10 +33,10 @@ class ImportConfigActivity : ComponentActivity() {
intent?.let(::readConfig)
}
override fun onNewIntent(intent: Intent?) {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Log.d(TAG, "onNewIntent: $intent")
intent?.let(::readConfig)
intent.let(::readConfig)
}
private fun readConfig(intent: Intent) {

View file

@ -25,5 +25,7 @@ object QtAndroidController {
external fun onConfigImported(data: String)
external fun onAuthResult(result: Boolean)
external fun decodeQrCode(data: String): Boolean
}

View file

@ -88,7 +88,7 @@ class NetworkState(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val numberAttempts = 3
val numberAttempts = 300
var attemptCount = 0
while(true) {
try {

View file

@ -27,7 +27,6 @@ link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.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}/core/installedAppsImageProvider.h
)
@ -35,7 +34,6 @@ set(HEADERS ${HEADERS}
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.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}/core/installedAppsImageProvider.cpp
)

View file

@ -98,6 +98,7 @@ bool AndroidController::initialize()
{"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)},
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
{"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)}
};
@ -210,6 +211,11 @@ void AndroidController::setScreenshotsEnabled(bool enabled)
callActivityMethod("setScreenshotsEnabled", "(Z)V", enabled);
}
void AndroidController::setNavigationBarColor(unsigned int color)
{
callActivityMethod("setNavigationBarColor", "(I)V", color);
}
void AndroidController::minimizeApp()
{
callActivityMethod("minimizeApp", "()V");
@ -265,6 +271,22 @@ void AndroidController::requestNotificationPermission()
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
jclass AndroidController::log;
jmethodID AndroidController::logDebug;
@ -462,6 +484,14 @@ void AndroidController::onConfigImported(JNIEnv *env, jobject thiz, jstring 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
bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data)
{

View file

@ -41,11 +41,13 @@ public:
void exportLogsFile(const QString &fileName);
void clearLogs();
void setScreenshotsEnabled(bool enabled);
void setNavigationBarColor(unsigned int color);
void minimizeApp();
QJsonArray getAppList();
QPixmap getAppIcon(const QString &package, QSize *size, const QSize &requestedSize);
bool isNotificationPermissionGranted();
void requestNotificationPermission();
bool requestAuthentication();
static bool initLogging();
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
@ -63,6 +65,7 @@ signals:
void configImported(QString config);
void importConfigFromOutside(QString config);
void initConnectionState(Vpn::ConnectionState state);
void authenticationResult(bool result);
private:
bool isWaitingStatus = true;
@ -89,6 +92,7 @@ private:
static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
static void onConfigImported(JNIEnv *env, jobject thiz, jstring data);
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);
template <typename Ret, typename ...Args>

View file

@ -1,16 +0,0 @@
#include "authResultReceiver.h"
AuthResultReceiver::AuthResultReceiver(QSharedPointer<AuthResultNotifier> &notifier) : 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();
}
}

View file

@ -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> &notifier);
void handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) override;
private:
QSharedPointer<AuthResultNotifier> m_notifier;
};
#endif // AUTHRESULTRECEIVER_H

View file

@ -10,9 +10,6 @@
#include "core/controllers/vpnConfigurationController.h"
#include "systemController.h"
#ifdef Q_OS_ANDROID
#include "platforms/android/android_utils.h"
#endif
#include "qrcodegen.hpp"
ExportController::ExportController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ContainersModel> &containersModel,
@ -24,12 +21,6 @@ ExportController::ExportController(const QSharedPointer<ServersModel> &serversMo
m_clientManagementModel(clientManagementModel),
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()
@ -63,26 +54,6 @@ void ExportController::generateFullAccessConfig()
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)
{
clearPreviousConfig();

View file

@ -6,9 +6,6 @@
#include "ui/models/clientManagementModel.h"
#include "ui/models/containers_model.h"
#include "ui/models/servers_model.h"
#ifdef Q_OS_ANDROID
#include "platforms/android/authResultReceiver.h"
#endif
class ExportController : public QObject
{
@ -25,9 +22,6 @@ public:
public slots:
void generateFullAccessConfig();
#if defined(Q_OS_ANDROID)
void generateFullAccessConfigAndroid();
#endif
void generateConnectionConfig(const QString &clientName);
void generateOpenVpnConfig(const QString &clientName);
void generateWireGuardConfig(const QString &clientName);
@ -74,11 +68,6 @@ private:
QString m_config;
QString m_nativeConfigString;
QList<QString> m_qrCodes;
#ifdef Q_OS_ANDROID
QSharedPointer<AuthResultNotifier> m_authResultNotifier;
QSharedPointer<QAndroidActivityResultReceiver> m_authResultReceiver;
#endif
};
#endif // EXPORTCONTROLLER_H

View file

@ -10,8 +10,6 @@
#ifdef Q_OS_ANDROID
#include "platforms/android/android_controller.h"
#include "platforms/android/android_utils.h"
#include <QJniObject>
#endif
#if defined Q_OS_MAC
#include "ui/macos_util.h"
@ -22,18 +20,8 @@ PageController::PageController(const QSharedPointer<ServersModel> &serversModel,
: QObject(parent), m_serversModel(serversModel), m_settings(settings)
{
#ifdef Q_OS_ANDROID
// Change color of navigation and status bar's
auto initialPageNavigationBarColor = getInitialPageNavigationBarColor();
AndroidUtils::runOnAndroidThreadSync([&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);
}
});
AndroidController::instance()->setNavigationBarColor(initialPageNavigationBarColor);
#endif
#if defined Q_OS_MACX
@ -115,14 +103,7 @@ unsigned int PageController::getInitialPageNavigationBarColor()
void PageController::updateNavigationBarColor(const int color)
{
#ifdef Q_OS_ANDROID
// Change color of navigation bar
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);
}
});
AndroidController::instance()->setNavigationBarColor(color);
#endif
}

View file

@ -285,3 +285,12 @@ QString SettingsController::getGatewayEndpoint()
{
return m_settings->getGatewayEndpoint();
}
bool SettingsController::isOnTv()
{
#ifdef Q_OS_ANDROID
return AndroidController::instance()->isOnTv();
#else
return false;
#endif
}

View file

@ -82,6 +82,8 @@ public slots:
void setGatewayEndpoint(const QString &endpoint);
QString getGatewayEndpoint();
bool isOnTv();
signals:
void primaryDnsChanged();
void secondaryDnsChanged();

View file

@ -125,3 +125,12 @@ void SystemController::setQmlRoot(QObject *qmlRoot)
{
m_qmlRoot = qmlRoot;
}
bool SystemController::isAuthenticated()
{
#ifdef Q_OS_ANDROID
return AndroidController::instance()->requestAuthentication();
#else
return true;
#endif
}

View file

@ -19,6 +19,7 @@ public slots:
void setQmlRoot(QObject *qmlRoot);
bool isAuthenticated();
signals:
void fileDialogClosed(const bool isAccepted);

View file

@ -14,6 +14,7 @@ Button {
property string defaultButtonColor: AmneziaStyle.color.paleGray
property string progressButtonColor: AmneziaStyle.color.paleGray
property string connectedButtonColor: AmneziaStyle.color.goldenApricot
property bool buttonActiveFocus: activeFocus && (Qt.platform.os !== "android" || SettingsController.isOnTv())
implicitWidth: 190
implicitHeight: 190
@ -50,14 +51,14 @@ Button {
verticalOffset: 0
radius: 10
samples: 25
color: root.activeFocus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.goldenApricot
color: root.buttonActiveFocus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.goldenApricot
source: backgroundCircle
}
ShapePath {
fillColor: AmneziaStyle.color.transparent
strokeColor: AmneziaStyle.color.paleGray
strokeWidth: root.activeFocus ? 1 : 0
strokeWidth: root.buttonActiveFocus ? 1 : 0
capStyle: ShapePath.RoundCap
PathAngleArc {
@ -81,14 +82,14 @@ Button {
return defaultButtonColor
}
}
strokeWidth: root.activeFocus ? 2 : 3
strokeWidth: root.buttonActiveFocus ? 2 : 3
capStyle: ShapePath.RoundCap
PathAngleArc {
centerX: backgroundCircle.width / 2
centerY: backgroundCircle.height / 2
radiusX: 93 - (root.activeFocus ? 2 : 0)
radiusY: 93 - (root.activeFocus ? 2 : 0)
radiusX: 93 - (root.buttonActiveFocus ? 2 : 0)
radiusY: 93 - (root.buttonActiveFocus ? 2 : 0)
startAngle: 0
sweepAngle: 360
}

View file

@ -140,22 +140,23 @@ PageType {
Keys.onTabPressed: lastItemTabClicked(focusItem)
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.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text
shareConnectionDrawer.open()
shareConnectionDrawer.contentVisible = false
PageController.showBusyIndicator(true)
if (Qt.platform.os === "android") {
ExportController.generateFullAccessConfigAndroid();
} else {
ExportController.generateFullAccessConfig();
}
shareConnectionDrawer.contentVisible = true
PageController.showBusyIndicator(false)
shareConnectionDrawer.contentVisible = true
}
}
}