diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallForegroundService.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallForegroundService.kt index 127f08bdc..fc6e1546e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallForegroundService.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallForegroundService.kt @@ -166,11 +166,17 @@ class CallForegroundService : Service() { runBlocking { withTimeoutOrNull(3_000L) { when (state) { - is CallState.IncomingCall -> manager.rejectCall() + is CallState.IncomingCall -> { + manager.rejectCall() + } + is CallState.Offering, is CallState.Connecting, is CallState.Connected, - -> manager.hangup() + -> { + manager.hangup() + } + else -> { /* nothing to do */ } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/notification/CallNotifier.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/notification/CallNotifier.kt index 0be21e0ed..529f11ef7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/notification/CallNotifier.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/notification/CallNotifier.kt @@ -176,6 +176,7 @@ object CallNotifier { ) { val callerUser = LocalCache.getOrCreateUser(callerPubKey) val callerName = callerUser.toBestDisplayName() + @Suppress("UNUSED_VARIABLE") val uri = "nostr:${callerPubKey.hexToByteArray().toNpub()}" 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 b5d850c43..a57e14b76 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 @@ -248,22 +248,26 @@ class CallActivity : AppCompatActivity() { // Best-effort on a detached scope. If the process // dies before completion, the remote 60s timeout // or our 65s watchdog handles it. - kotlinx.coroutines.CoroutineScope( - kotlinx.coroutines.SupervisorJob() + kotlinx.coroutines.Dispatchers.Main.immediate, - ).launch { - manager.rejectCall() - } + kotlinx.coroutines + .CoroutineScope( + kotlinx.coroutines.SupervisorJob() + kotlinx.coroutines.Dispatchers.Main.immediate, + ).launch { + manager.rejectCall() + } } + is CallState.Offering, is CallState.Connecting, is CallState.Connected, -> { - kotlinx.coroutines.CoroutineScope( - kotlinx.coroutines.SupervisorJob() + kotlinx.coroutines.Dispatchers.Main.immediate, - ).launch { - manager.hangup() - } + kotlinx.coroutines + .CoroutineScope( + kotlinx.coroutines.SupervisorJob() + kotlinx.coroutines.Dispatchers.Main.immediate, + ).launch { + manager.hangup() + } } + else -> {} } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/call/session/CallSession.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/call/session/CallSession.kt index bde72db6f..1b48ba93e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/call/session/CallSession.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/call/session/CallSession.kt @@ -113,6 +113,7 @@ class CallSession( val isBluetoothAvailable: StateFlow = audioManager.isBluetoothAvailable @Volatile private var videoPausedByProximity = false + @Volatile private var closed = false @Volatile private var foregroundServiceStarted = false @@ -147,16 +148,25 @@ class CallSession( callManager.sessionEvents.collect { event -> if (closed) return@collect when (event) { - is com.vitorpamplona.amethyst.commons.call.CallSessionEvent.AnswerReceived -> + is com.vitorpamplona.amethyst.commons.call.CallSessionEvent.AnswerReceived -> { onCallAnswerReceived(event.event.pubKey, event.event.sdpAnswer()) - is com.vitorpamplona.amethyst.commons.call.CallSessionEvent.IceCandidateReceived -> + } + + is com.vitorpamplona.amethyst.commons.call.CallSessionEvent.IceCandidateReceived -> { onIceCandidateReceived(event.event) - is com.vitorpamplona.amethyst.commons.call.CallSessionEvent.NewPeerInGroupCall -> + } + + is com.vitorpamplona.amethyst.commons.call.CallSessionEvent.NewPeerInGroupCall -> { onNewPeerInGroupCall(event.peerPubKey) - is com.vitorpamplona.amethyst.commons.call.CallSessionEvent.MidCallOfferReceived -> + } + + is com.vitorpamplona.amethyst.commons.call.CallSessionEvent.MidCallOfferReceived -> { onMidCallOfferReceived(event.peerPubKey, event.sdpOffer) - is com.vitorpamplona.amethyst.commons.call.CallSessionEvent.PeerLeft -> + } + + is com.vitorpamplona.amethyst.commons.call.CallSessionEvent.PeerLeft -> { disposePeerSession(event.peerPubKey) + } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index e20d91497..510234327 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -41,7 +41,6 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.call.CallState - import com.vitorpamplona.amethyst.service.crashreports.DisplayCrashMessages import com.vitorpamplona.amethyst.service.relayClient.notifyCommand.compose.DisplayNotifyMessages import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataScreen diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 06d85ae33..d390bf1ab 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -67,7 +67,6 @@ import com.vitorpamplona.amethyst.model.privacyOptions.RoleBasedHttpClientBuilde import com.vitorpamplona.amethyst.service.OnlineChecker import com.vitorpamplona.amethyst.service.ZapPaymentHandler import com.vitorpamplona.amethyst.service.broadcast.BroadcastTracker - import com.vitorpamplona.amethyst.service.cashu.CashuToken import com.vitorpamplona.amethyst.service.cashu.melt.MeltProcessor import com.vitorpamplona.amethyst.service.checkNotInMainThread diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/call/CallManager.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/call/CallManager.kt index c28521e06..ed0ce984c 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/call/CallManager.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/call/CallManager.kt @@ -95,11 +95,12 @@ class CallManager( private val _sessionEvents = MutableSharedFlow(extraBufferCapacity = 256) val sessionEvents: SharedFlow = _sessionEvents.asSharedFlow() - /** + /* * Renegotiation offers from remote peers. Separate from [sessionEvents] * because renegotiation has its own glare-resolution logic in * CallSession and benefits from a dedicated collector. */ + /** Emits a session event, logging a warning if the buffer overflows. */ private fun emitSessionEvent(event: CallSessionEvent) { if (!emitSessionEvent(event)) { diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/call/CallSessionEvent.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/call/CallSessionEvent.kt index 69488a250..2abd92748 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/call/CallSessionEvent.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/call/CallSessionEvent.kt @@ -31,17 +31,28 @@ import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallIceCandidateEvent */ sealed interface CallSessionEvent { /** A peer answered our offer (or we received an answer in a group call). */ - data class AnswerReceived(val event: CallAnswerEvent) : CallSessionEvent + data class AnswerReceived( + val event: CallAnswerEvent, + ) : CallSessionEvent /** An ICE candidate arrived from a peer. */ - data class IceCandidateReceived(val event: CallIceCandidateEvent) : CallSessionEvent + data class IceCandidateReceived( + val event: CallIceCandidateEvent, + ) : CallSessionEvent /** A new peer joined the group call and needs callee-to-callee mesh setup. */ - data class NewPeerInGroupCall(val peerPubKey: HexKey) : CallSessionEvent + data class NewPeerInGroupCall( + val peerPubKey: HexKey, + ) : CallSessionEvent /** A mid-call offer arrived from another callee in a group call. */ - data class MidCallOfferReceived(val peerPubKey: HexKey, val sdpOffer: String) : CallSessionEvent + data class MidCallOfferReceived( + val peerPubKey: HexKey, + val sdpOffer: String, + ) : CallSessionEvent /** A peer left the call (hangup/reject/timeout) but the call continues. */ - data class PeerLeft(val peerPubKey: HexKey) : CallSessionEvent + data class PeerLeft( + val peerPubKey: HexKey, + ) : CallSessionEvent } diff --git a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/call/CallManagerTest.kt b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/call/CallManagerTest.kt index 222cd68b8..37afc7503 100644 --- a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/call/CallManagerTest.kt +++ b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/call/CallManagerTest.kt @@ -1167,7 +1167,12 @@ class CallManagerTest { assertIs(state) assertTrue(carol in state.peerPubKeys, "Mid-call joiner must be added to peerPubKeys") assertTrue(alice in state.peerPubKeys, "Existing peer must still be present") - val forwardedPeer = events.filterIsInstance().firstOrNull()?.event?.pubKey + val forwardedPeer = + events + .filterIsInstance() + .firstOrNull() + ?.event + ?.pubKey assertEquals(carol, forwardedPeer, "Answer must still be emitted to CallSession") job.cancel() } @@ -1318,7 +1323,12 @@ class CallManagerTest { val bobAfterCarolAnswer = bobManager.state.value assertIs(bobAfterCarolAnswer) assertTrue(carol in bobAfterCarolAnswer.peerPubKeys, "Bob should add Carol to his membership") - val bobForwardedAnswer = bobEvents.filterIsInstance().firstOrNull()?.event?.pubKey + val bobForwardedAnswer = + bobEvents + .filterIsInstance() + .firstOrNull() + ?.event + ?.pubKey assertEquals(carol, bobForwardedAnswer, "Bob must emit Carol's answer to his CallSession") bobJob.cancel() }