diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt index a28d531a3..1d2e09ccb 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -343,12 +343,22 @@ class AmneziaActivity : QtActivity() { resumeHandler.removeCallbacksAndMessages(null) openFileDeliveryScheduled = false Log.d(TAG, "Pause Amnezia activity") + // Notify Qt to stop rendering before the EGL surface is disconnected + mainScope.launch { + qtInitialized.await() + QtAndroidController.onActivityPaused() + } } override fun onResume() { super.onResume() isActivityResumed = true Log.d(TAG, "Resume Amnezia activity") + // Notify Qt to resume rendering after surface reconnects + mainScope.launch { + qtInitialized.await() + QtAndroidController.onActivityResumed() + } if (pendingOpenFileUri != null && !openFileDeliveryScheduled) { val uri = pendingOpenFileUri!! diff --git a/client/android/src/org/amnezia/vpn/qt/QtAndroidController.kt b/client/android/src/org/amnezia/vpn/qt/QtAndroidController.kt index b77e77d66..ec143635d 100644 --- a/client/android/src/org/amnezia/vpn/qt/QtAndroidController.kt +++ b/client/android/src/org/amnezia/vpn/qt/QtAndroidController.kt @@ -31,4 +31,7 @@ object QtAndroidController { external fun onImeInsetsChanged(heightDp: Int) external fun onSystemBarsInsetsChanged(navBarHeightDp: Int, statusBarHeightDp: Int) + + external fun onActivityPaused() + external fun onActivityResumed() } \ No newline at end of file diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index 2d60ad849..c6f538bd9 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -101,7 +101,9 @@ bool AndroidController::initialize() {"onAuthResult", "(Z)V", reinterpret_cast(onAuthResult)}, {"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast(decodeQrCode)}, {"onImeInsetsChanged", "(I)V", reinterpret_cast(onImeInsetsChanged)}, - {"onSystemBarsInsetsChanged", "(II)V", reinterpret_cast(onSystemBarsInsetsChanged)} + {"onSystemBarsInsetsChanged", "(II)V", reinterpret_cast(onSystemBarsInsetsChanged)}, + {"onActivityPaused", "()V", reinterpret_cast(onActivityPaused)}, + {"onActivityResumed", "()V", reinterpret_cast(onActivityResumed)} }; QJniEnvironment env; @@ -558,3 +560,22 @@ void AndroidController::onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jin emit AndroidController::instance()->systemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp); } +// static +void AndroidController::onActivityPaused(JNIEnv *env, jobject thiz) +{ + Q_UNUSED(env); + Q_UNUSED(thiz); + + emit AndroidController::instance()->activityPaused(); +} + +// static +void AndroidController::onActivityResumed(JNIEnv *env, jobject thiz) +{ + Q_UNUSED(env); + Q_UNUSED(thiz); + + emit AndroidController::instance()->activityResumed(); +} + + diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h index a5a4b3d23..49360f02c 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -75,6 +75,8 @@ signals: void authenticationResult(bool result); void imeInsetsChanged(int heightDp); void systemBarsInsetsChanged(int navBarHeightDp, int statusBarHeightDp); + void activityPaused(); + void activityResumed(); private: bool isWaitingStatus = true; @@ -105,6 +107,8 @@ private: static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data); static void onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp); static void onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp); + static void onActivityPaused(JNIEnv *env, jobject thiz); + static void onActivityResumed(JNIEnv *env, jobject thiz); template static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args); diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 8b456f3a6..5363ab220 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -45,6 +45,8 @@ SettingsController::SettingsController(const QSharedPointer &serve emit safeAreaBottomMarginChanged(); emit safeAreaTopMarginChanged(); }); + connect(AndroidController::instance(), &AndroidController::activityPaused, this, &SettingsController::activityPaused); + connect(AndroidController::instance(), &AndroidController::activityResumed, this, &SettingsController::activityResumed); #endif m_isDevModeEnabled = m_settings->isDevGatewayEnv(); diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index fa50bc23c..ed20a1e6b 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -141,6 +141,9 @@ signals: void safeAreaTopMarginChanged(); void safeAreaBottomMarginChanged(); + void activityPaused(); + void activityResumed(); + void isHomeAdLabelVisibleChanged(bool visible); void startMinimizedChanged(); diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index a95044d91..89b2bb98c 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -23,17 +23,25 @@ Window { if (Qt.application.state === Qt.ApplicationActive) { root.visible = true refreshTimer.restart() - } else if (Qt.application.state === Qt.ApplicationSuspended) { - // Hide window to stop the Qt render loop and prevent - // eglSwapBuffers from being called on a lost EGL context. - // NOTE: Do NOT hide on ApplicationInactive — that fires on any - // focus change (IME, notifications) and would blank the screen. - root.visible = false } } } } + // Hide the window immediately when Android Activity.onPause() fires so that + // Qt's render loop stops before the EGL surface is disconnected. This + // prevents "QRhiGles2: Failed to make context current" and the resulting + // black screen that appears after swiping home and returning. + Connections { + target: SettingsController + function onActivityPaused() { + if (Qt.platform.os === "android") root.visible = false + } + function onActivityResumed() { + if (Qt.platform.os === "android") root.visible = true + } + } + Timer { id: refreshTimer interval: 150