From 2d0b36ec77e7f6a72d1990ce4387dad46d811b01 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Fri, 20 Feb 2026 19:21:44 +0530 Subject: [PATCH 1/2] fix: Reject current active call when telecom puts it to inactive or hold --- .../getstream/video/android/core/CallState.kt | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt index d6ec114a75..8cebf886dc 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt @@ -116,6 +116,7 @@ import io.getstream.video.android.core.notifications.IncomingNotificationData import io.getstream.video.android.core.notifications.NotificationType import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig import io.getstream.video.android.core.notifications.internal.telecom.jetpack.JetpackTelecomRepository +import io.getstream.video.android.core.notifications.internal.telecom.jetpack.TelecomCall import io.getstream.video.android.core.permission.PermissionRequest import io.getstream.video.android.core.pinning.PinType import io.getstream.video.android.core.pinning.PinUpdateAtTime @@ -130,6 +131,7 @@ import io.getstream.video.android.core.utils.toUser import io.getstream.video.android.model.StreamCallId import io.getstream.video.android.model.User import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.currentCoroutineContext @@ -145,6 +147,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transform import kotlinx.coroutines.isActive @@ -720,8 +723,19 @@ public class CallState( @InternalStreamVideoApi public val notificationIdFlow: StateFlow = _notificationIdFlow + private var telecomHoldObserverJob: Job? = null + @InternalStreamVideoApi internal var jetpackTelecomRepository: JetpackTelecomRepository? = null + set(value) { + field = value + if (value != null) { + observeTelecomHold(value) + } else { + telecomHoldObserverJob?.cancel() + telecomHoldObserverJob = null + } + } internal var incomingNotificationData = IncomingNotificationData(emptyMap()) @@ -1749,6 +1763,28 @@ public class CallState( this._notificationIdFlow.value = notificationId this.atomicNotification.set(notification) } + + /** + * [RingingState.Incoming] and [RingingState.Outgoing] are intentionally not observed. + * In Android Telecom, hold states are only applicable once a call is active (answered). + */ + private fun observeTelecomHold(repo: JetpackTelecomRepository) { + telecomHoldObserverJob?.cancel() + + telecomHoldObserverJob = scope.launch(Dispatchers.Default) { + repo.currentCall + .map { (it as? TelecomCall.Registered)?.isOnHold == true } + .distinctUntilChanged() + .collect { telecomCall -> + when (ringingState.value) { + is RingingState.Active -> { + call.leave("call-on-hold") + } + else -> {} + } + } + } + } } private fun MemberResponse.toMemberState(): MemberState { From 1858ff77999a76d1b3f0a5c2ba51e9afedbe6c82 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 24 Feb 2026 16:40:49 +0530 Subject: [PATCH 2/2] chore: improve code semantic --- .../main/kotlin/io/getstream/video/android/core/CallState.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt index 8cebf886dc..0dc19bb8aa 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt @@ -146,6 +146,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -1775,7 +1776,8 @@ public class CallState( repo.currentCall .map { (it as? TelecomCall.Registered)?.isOnHold == true } .distinctUntilChanged() - .collect { telecomCall -> + .filter { it } + .collect { isOnHold -> when (ringingState.value) { is RingingState.Active -> { call.leave("call-on-hold")