Skip to content

Commit 4aa6d71

Browse files
Improve Call Screen Behavior on Decline (#1478)
* bugfix: Handle incoming call from OnNewIntent in StreamCallActivity * bugfix: Handle incoming call from OnNewIntent in StreamCallActivity * bugfix: Handle incoming call from OnNewIntent in StreamCallActivity * bugfix: Handle incoming call from OnNewIntent in StreamCallActivity * bugfix: Handle Group call rejection on Android UseCase: Ios is making a group call with 2 android users and one of the android user will decline * api dump * api dump * minor update * Call reject in bigger lifecycle scope * fix: Avoid double finish by adding a safe finish * fix: Deprecate methods which didn't had call object * temp: Introduce a way to handle multiple calls in the activity. Pending: This is not backward compatible yet * temp: Remove deprecation checkpoint 1 * temp: api dump * temp: Add StreamCallActivityException * temp: spotless * temp: spotless * chore: Update kdoc * chore: Update kdoc * chore: Update kdoc * chore: Update kdoc * chore: Update kdoc * chore: Update kdoc --------- Co-authored-by: Aleksandar Apostolov <apostolov.alexandar@gmail.com>
1 parent 0c78e46 commit 4aa6d71

10 files changed

Lines changed: 558 additions & 93 deletions

File tree

demo-app/src/main/kotlin/io/getstream/video/android/CallActivity.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ import kotlinx.coroutines.runBlocking
4848
class CallActivity : ComposeStreamCallActivity() {
4949

5050
override val uiDelegate: StreamActivityUiDelegate<StreamCallActivity> = StreamDemoUiDelegate()
51-
override val configuration: StreamCallActivityConfiguration =
52-
StreamCallActivityConfiguration(
53-
closeScreenOnCallEnded = false,
54-
canSkipPermissionRationale = false,
55-
)
51+
52+
override fun loadConfigFromIntent(intent: Intent?): StreamCallActivityConfiguration {
53+
return super.loadConfigFromIntent(intent)
54+
.copy(closeScreenOnCallEnded = false, canSkipPermissionRationale = false)
55+
}
5656

5757
override fun onNewIntent(intent: Intent) {
5858
super.onNewIntent(intent)
@@ -144,7 +144,7 @@ class CallActivity : ComposeStreamCallActivity() {
144144
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
145145
}
146146
startActivity(intent)
147-
finish()
147+
safeFinish()
148148
}
149149
}
150150
}

demo-app/src/main/kotlin/io/getstream/video/android/MainActivity.kt

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@ import io.getstream.video.android.tooling.util.StreamBuildFlavorUtil
4141
import io.getstream.video.android.ui.AppNavHost
4242
import io.getstream.video.android.ui.AppScreens
4343
import io.getstream.video.android.ui.common.StreamCallActivity
44+
import io.getstream.video.android.ui.common.StreamCallActivityConfiguration
4445
import io.getstream.video.android.util.InAppUpdateHelper
4546
import io.getstream.video.android.util.InstallReferrer
4647
import kotlinx.coroutines.ExperimentalCoroutinesApi
4748
import kotlinx.coroutines.flow.collectLatest
49+
import kotlinx.coroutines.flow.emptyFlow
4850
import kotlinx.coroutines.flow.firstOrNull
4951
import kotlinx.coroutines.flow.flatMapLatest
5052
import kotlinx.coroutines.flow.flowOf
@@ -104,30 +106,42 @@ class MainActivity : ComponentActivity() {
104106
observeIncomingCall()
105107
}
106108

109+
/**
110+
* Observes incoming calls in real-time.
111+
*
112+
* - First, it waits for the StreamVideo instance to be available.
113+
* - Then it watches for a "ringing" call (i.e., someone is calling).
114+
* - Once a ringing call is found, it listens for its ringing state updates.
115+
* - If the ringing state changes to "Incoming", it means we are receiving a call.
116+
* - At that point, it starts the incoming call screen using `startIncomingCallActivity()`.
117+
*
118+
* This flow automatically stops and restarts if the instance or call changes,
119+
* ensuring we always react to the latest incoming call state.
120+
*/
107121
@OptIn(ExperimentalCoroutinesApi::class)
108122
private fun observeIncomingCall() {
109123
lifecycleScope.launch {
110-
StreamVideo.instanceState.flatMapLatest { instance ->
111-
instance?.state?.ringingCall ?: flowOf(null)
112-
}.collectLatest { call ->
113-
if (call != null) {
114-
lifecycleScope.launch {
115-
// Monitor the ringingState on a non-null call
116-
call.state.ringingState.collectLatest {
117-
if (it is RingingState.Incoming) {
118-
startIncomingCallActivity(call)
119-
}
120-
}
124+
StreamVideo.instanceState
125+
.flatMapLatest { instance ->
126+
instance?.state?.ringingCall ?: flowOf(null)
127+
}
128+
.flatMapLatest { call ->
129+
call?.state?.ringingState ?: emptyFlow()
130+
}
131+
.collectLatest { ringingState ->
132+
val currentCall = StreamVideo.instanceState.value?.state?.ringingCall?.value
133+
if (ringingState is RingingState.Incoming) {
134+
currentCall?.let { startIncomingCallActivity(it) }
121135
}
122136
}
123-
}
124137
}
125138
}
126139

127140
fun startIncomingCallActivity(call: Call) {
128141
val intent = StreamCallActivity.callIntent(
129142
context = this,
130143
cid = StreamCallId.fromCallCid(call.cid),
144+
configuration = StreamCallActivityConfiguration(closeScreenOnCallEnded = true),
131145
members = emptyList(),
132146
leaveWhenLastInCall = true,
133147
action = NotificationHandler.ACTION_INCOMING_CALL,

stream-video-android-core/api/stream-video-android-core.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5647,6 +5647,7 @@ public final class io/getstream/video/android/core/CallState {
56475647
public final fun updateParticipantSortingOrder (Ljava/util/Comparator;)V
56485648
public final fun updateParticipantVisibility (Ljava/lang/String;Lio/getstream/video/android/core/model/VisibilityOnScreenState;)V
56495649
public final fun updateParticipantVisibilityFlow (Lkotlinx/coroutines/flow/Flow;)V
5650+
public final fun updateRejectedBy (Ljava/util/Set;)V
56505651
public final fun upsertParticipants (Ljava/util/List;)V
56515652
}
56525653

stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,6 +1525,10 @@ public class CallState(
15251525
null
15261526
}
15271527
}
1528+
1529+
fun updateRejectedBy(userId: Set<String>) {
1530+
_rejectedBy.value = userId
1531+
}
15281532
}
15291533

15301534
private fun MemberResponse.toMemberState(): MemberState {

stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/StreamCallActivityComposeDelegate.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ public open class StreamCallActivityComposeDelegate : StreamCallActivityComposeU
190190
centerContent = { },
191191
onCallAction = {
192192
call.leave()
193-
finish()
193+
safeFinish()
194194
},
195195
)
196196
}
@@ -332,7 +332,7 @@ public open class StreamCallActivityComposeDelegate : StreamCallActivityComposeU
332332
) {
333333
if (!showRationale && configuration.canSkipPermissionRationale) {
334334
logger.w { "Permissions were not granted, but rationale is required to be skipped." }
335-
finish()
335+
safeFinish()
336336
} else {
337337
PermissionsRationaleContent(call, granted, notGranted)
338338
}
@@ -355,7 +355,7 @@ public open class StreamCallActivityComposeDelegate : StreamCallActivityComposeU
355355
CallDisconnectedContent(call)
356356
} else {
357357
// This is just for safety, will be called from other place as well.
358-
finish()
358+
safeFinish()
359359
}
360360
}
361361

@@ -365,7 +365,7 @@ public open class StreamCallActivityComposeDelegate : StreamCallActivityComposeU
365365
CallFailedContent(call, err)
366366
} else {
367367
// This is just for safety, will be called from other place as well.
368-
finish()
368+
safeFinish()
369369
}
370370
}
371371

@@ -472,14 +472,14 @@ public open class StreamCallActivityComposeDelegate : StreamCallActivityComposeU
472472
override fun StreamCallActivity.CallFailedContent(call: Call, exception: java.lang.Exception) {
473473
// By default we finish the activity regardless of config.
474474
// There is not default UI for call failed content.
475-
finish()
475+
safeFinish()
476476
}
477477

478478
@Composable
479479
override fun StreamCallActivity.CallDisconnectedContent(call: Call) {
480480
// By default we finish the activity regardless of config.
481481
// There is not default UI for call ended content.
482-
finish()
482+
safeFinish()
483483
}
484484

485485
@Composable

0 commit comments

Comments
 (0)