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