diff --git a/amethyst/src/main/AndroidManifest.xml b/amethyst/src/main/AndroidManifest.xml
index 26d8143a7d..986175094a 100644
--- a/amethyst/src/main/AndroidManifest.xml
+++ b/amethyst/src/main/AndroidManifest.xml
@@ -250,7 +250,7 @@
manager.rejectCall()
+ is CallState.Offering,
+ is CallState.Connecting,
+ is CallState.Connected,
+ -> manager.hangup()
+ else -> { /* nothing to do */ }
+ }
+ }
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "publishHangupBlocking failed", e)
+ }
+ }
+
private fun createNotificationChannel() {
val channel =
NotificationChannel(
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallSessionBridge.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallSessionBridge.kt
index e2a0546bf4..b4229d41ab 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallSessionBridge.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallSessionBridge.kt
@@ -23,10 +23,9 @@ package com.vitorpamplona.amethyst.service.call
import com.vitorpamplona.amethyst.commons.call.CallManager
import com.vitorpamplona.amethyst.commons.call.CallState
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.launch
+import com.vitorpamplona.quartz.utils.Log
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeoutOrNull
/**
* Process-level singleton that bridges the active [CallManager] and
@@ -67,8 +66,12 @@ object CallSessionBridge {
state is CallState.Connecting ||
state is CallState.Connected
) {
- CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate).launch {
- mgr.hangup()
+ try {
+ runBlocking {
+ withTimeoutOrNull(3_000L) { mgr.hangup() }
+ }
+ } catch (e: Exception) {
+ Log.e("CallSessionBridge", "clear: hangup failed", e)
}
}
}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/call/CallActivity.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/call/CallActivity.kt
index f6c8d2bb68..c6c10c3f56 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/call/CallActivity.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/call/CallActivity.kt
@@ -49,9 +49,6 @@ import com.vitorpamplona.amethyst.ui.screen.ManageRelayServices
import com.vitorpamplona.amethyst.ui.screen.ManageWebOkHttp
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
import com.vitorpamplona.quartz.nipACWebRtcCalls.tags.CallType
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
class CallActivity : AppCompatActivity() {
@@ -228,40 +225,17 @@ class CallActivity : AppCompatActivity() {
override fun onDestroy() {
unregisterPipReceiver()
- val manager = CallSessionBridge.callManager
-
- // Release all WebRTC/audio/notification resources FIRST, before
- // the signaling hangup. close() is synchronous and runs before
- // super.onDestroy() cancels lifecycleScope, so the init
- // collectors are still alive but close() gets to run first.
+ // Release all WebRTC/audio/notification resources. close() is
+ // synchronous and runs before super.onDestroy() cancels
+ // lifecycleScope.
session?.close()
session = null
- // No callback nulling needed — CallSession collects from
- // SharedFlows; the `closed` flag prevents processing after close().
-
- // Publish reject/hangup so the remote side stops ringing.
- // Use goAsync-style: the hangup is best-effort. If the process
- // dies before it completes, the watchdog on the remote side
- // (or our own CallManager watchdog) handles cleanup.
- if (manager != null) {
- when (manager.state.value) {
- is CallState.IncomingCall -> {
- CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate).launch {
- manager.rejectCall()
- }
- }
- is CallState.Offering,
- is CallState.Connecting,
- is CallState.Connected,
- -> {
- CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate).launch {
- manager.hangup()
- }
- }
- else -> {}
- }
- }
+ // Hangup signaling is NOT published here. It is handled by
+ // CallForegroundService.onTaskRemoved() / onDestroy() which
+ // runs synchronously via runBlocking with a 3-second timeout.
+ // This is more reliable than the previous fire-and-forget
+ // CoroutineScope that could be killed before completing.
super.onDestroy()
}