diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api
index 8b296f82a4..833485348b 100644
--- a/stream-video-android-core/api/stream-video-android-core.api
+++ b/stream-video-android-core/api/stream-video-android-core.api
@@ -4139,7 +4139,8 @@ public class io/getstream/video/android/core/notifications/DefaultNotificationHa
public final fun getHideRingingNotificationInForeground ()Z
public final fun getNotificationIconRes ()I
protected final fun getNotificationManager ()Landroidx/core/app/NotificationManagerCompat;
- public fun getOngoingCallNotification (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;IZ)Landroid/app/Notification;
+ public fun getNotificationUpdates (Lkotlinx/coroutines/CoroutineScope;Lio/getstream/video/android/core/Call;Lio/getstream/video/android/model/User;Lkotlin/jvm/functions/Function1;)V
+ public fun getOngoingCallNotification (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;ZI)Landroid/app/Notification;
public fun getRingingCallNotification (Lio/getstream/video/android/core/RingingState;Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Z)Landroid/app/Notification;
public fun getSettingUpCallNotification ()Landroid/app/Notification;
public fun onLiveCall (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;)V
@@ -4189,8 +4190,9 @@ public abstract interface class io/getstream/video/android/core/notifications/No
public static final field INTENT_EXTRA_CALL_CID Ljava/lang/String;
public static final field INTENT_EXTRA_CALL_DISPLAY_NAME Ljava/lang/String;
public static final field INTENT_EXTRA_NOTIFICATION_ID Ljava/lang/String;
- public abstract fun getOngoingCallNotification (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;IZ)Landroid/app/Notification;
- public static synthetic fun getOngoingCallNotification$default (Lio/getstream/video/android/core/notifications/NotificationHandler;Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;IZILjava/lang/Object;)Landroid/app/Notification;
+ public abstract fun getNotificationUpdates (Lkotlinx/coroutines/CoroutineScope;Lio/getstream/video/android/core/Call;Lio/getstream/video/android/model/User;Lkotlin/jvm/functions/Function1;)V
+ public abstract fun getOngoingCallNotification (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;ZI)Landroid/app/Notification;
+ public static synthetic fun getOngoingCallNotification$default (Lio/getstream/video/android/core/notifications/NotificationHandler;Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;ZIILjava/lang/Object;)Landroid/app/Notification;
public abstract fun getRingingCallNotification (Lio/getstream/video/android/core/RingingState;Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Z)Landroid/app/Notification;
public static synthetic fun getRingingCallNotification$default (Lio/getstream/video/android/core/notifications/NotificationHandler;Lio/getstream/video/android/core/RingingState;Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;ZILjava/lang/Object;)Landroid/app/Notification;
public abstract fun getSettingUpCallNotification ()Landroid/app/Notification;
diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/DefaultNotificationHandler.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/DefaultNotificationHandler.kt
index 6190e3b30b..badb033f05 100644
--- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/DefaultNotificationHandler.kt
+++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/DefaultNotificationHandler.kt
@@ -36,6 +36,7 @@ import androidx.core.graphics.drawable.IconCompat
import io.getstream.android.push.permissions.DefaultNotificationPermissionHandler
import io.getstream.android.push.permissions.NotificationPermissionHandler
import io.getstream.log.taggedLogger
+import io.getstream.video.android.core.Call
import io.getstream.video.android.core.R
import io.getstream.video.android.core.RingingState
import io.getstream.video.android.core.notifications.NotificationHandler.Companion.ACTION_LIVE_CALL
@@ -44,6 +45,13 @@ import io.getstream.video.android.core.notifications.NotificationHandler.Compani
import io.getstream.video.android.core.notifications.internal.DefaultStreamIntentResolver
import io.getstream.video.android.core.notifications.internal.service.CallService
import io.getstream.video.android.model.StreamCallId
+import io.getstream.video.android.model.User
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.launch
public open class DefaultNotificationHandler(
private val application: Application,
@@ -107,7 +115,7 @@ public open class DefaultNotificationHandler(
override fun getRingingCallNotification(
ringingState: RingingState,
callId: StreamCallId,
- callDisplayName: String,
+ callDisplayName: String?,
shouldHaveContentIntent: Boolean,
): Notification? {
return if (ringingState is RingingState.Incoming) {
@@ -183,7 +191,7 @@ public open class DefaultNotificationHandler(
fullScreenPendingIntent: PendingIntent,
acceptCallPendingIntent: PendingIntent,
rejectCallPendingIntent: PendingIntent,
- callerName: String,
+ callerName: String?,
shouldHaveContentIntent: Boolean,
): Notification {
// if the app is in foreground then don't interrupt the user with a high priority
@@ -233,7 +241,7 @@ public open class DefaultNotificationHandler(
priority = NotificationCompat.PRIORITY_HIGH
setContentTitle(callerName)
setContentText(
- application.getString(R.string.stream_video_incoming_call_notification_title),
+ application.getString(R.string.stream_video_incoming_call_notification_description),
)
setChannelId(channelId)
setOngoing(true)
@@ -282,8 +290,8 @@ public open class DefaultNotificationHandler(
override fun getOngoingCallNotification(
callId: StreamCallId,
callDisplayName: String?,
- remoteParticipantCount: Int,
isOutgoingCall: Boolean,
+ remoteParticipantCount: Int,
): Notification? {
val notificationId = callId.hashCode() // Notification ID
@@ -344,14 +352,97 @@ public open class DefaultNotificationHandler(
)
.setAutoCancel(false)
.setOngoing(true)
- .addHangupAction(
+ .addHangUpAction(
hangUpIntent,
- callDisplayName ?: callId.toString(),
+ callDisplayName ?: application.getString(
+ R.string.stream_video_ongoing_call_notification_title,
+ ),
remoteParticipantCount,
)
.build()
}
+ override fun getNotificationUpdates(
+ coroutineScope: CoroutineScope,
+ call: Call,
+ localUser: User,
+ onUpdate: (Notification) -> Unit,
+ ) {
+ coroutineScope.launch {
+ var latestRemoteParticipantCount = -1
+
+ // Monitor call state and remote participants
+ combine(
+ call.state.ringingState,
+ call.state.members,
+ call.state.remoteParticipants,
+ ) { ringingState, members, remoteParticipants ->
+ Triple(ringingState, members, remoteParticipants)
+ }
+ .distinctUntilChanged()
+ .filter { it.first is RingingState.Active || it.first is RingingState.Outgoing }
+ .collectLatest { state ->
+ val ringingState = state.first
+ val members = state.second
+ val remoteParticipants = state.third
+
+ if (ringingState is RingingState.Outgoing) {
+ val remoteMembersCount = members.size - 1
+
+ val callDisplayName = if (remoteMembersCount != 1) {
+ application.getString(
+ R.string.stream_video_outgoing_call_notification_title,
+ )
+ } else {
+ members.firstOrNull { member ->
+ member.user.id != localUser.id
+ }?.user?.name ?: "Unknown"
+ }
+
+ getOngoingCallNotification(
+ callId = StreamCallId.fromCallCid(call.cid),
+ callDisplayName = callDisplayName,
+ isOutgoingCall = true,
+ remoteParticipantCount = remoteMembersCount,
+ )?.let {
+ onUpdate(it)
+ }
+ } else if (ringingState is RingingState.Active) {
+ // If number of remote participants increased or decreased
+ if (remoteParticipants.size != latestRemoteParticipantCount) {
+ latestRemoteParticipantCount = remoteParticipants.size
+
+ val callDisplayName = if (remoteParticipants.isEmpty()) {
+ // If no remote participants, get simple call notification title
+ application.getString(
+ R.string.stream_video_ongoing_call_notification_title,
+ )
+ } else {
+ if (remoteParticipants.size > 1) {
+ // If more than 1 remote participant, get group call notification title
+ application.getString(
+ R.string.stream_video_ongoing_group_call_notification_title,
+ )
+ } else {
+ // If 1 remote participant, get the name of the remote participant
+ remoteParticipants.firstOrNull()?.name?.value ?: "Unknown"
+ }
+ }
+
+ // Use latest call display name in notification
+ getOngoingCallNotification(
+ callId = StreamCallId.fromCallCid(call.cid),
+ callDisplayName = callDisplayName,
+ remoteParticipantCount = remoteParticipants.size,
+ )?.let {
+ onUpdate(it)
+ }
+ }
+ }
+ }
+ }
+ }
+
private fun maybeCreateChannel(
channelId: String,
context: Context,
@@ -427,7 +518,7 @@ public open class DefaultNotificationHandler(
.build()
}
- private fun NotificationCompat.Builder.addHangupAction(
+ private fun NotificationCompat.Builder.addHangUpAction(
rejectCallPendingIntent: PendingIntent,
callDisplayName: String,
remoteParticipantCount: Int,
@@ -474,13 +565,23 @@ public open class DefaultNotificationHandler(
private fun NotificationCompat.Builder.addCallActions(
acceptCallPendingIntent: PendingIntent,
rejectCallPendingIntent: PendingIntent,
- callDisplayName: String,
+ callDisplayName: String?,
): NotificationCompat.Builder = apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
setStyle(
CallStyle.forIncomingCall(
Person.Builder()
- .setName(callDisplayName)
+ .setName(callDisplayName ?: "Unknown")
+ .apply {
+ if (callDisplayName == null) {
+ setIcon(
+ IconCompat.createWithResource(
+ application,
+ R.drawable.stream_video_ic_user,
+ ),
+ )
+ }
+ }
.build(),
rejectCallPendingIntent,
acceptCallPendingIntent,
diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/NotificationHandler.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/NotificationHandler.kt
index 686f489e76..5646bc2eb8 100644
--- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/NotificationHandler.kt
+++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/NotificationHandler.kt
@@ -18,8 +18,11 @@ package io.getstream.video.android.core.notifications
import android.app.Notification
import io.getstream.android.push.permissions.NotificationPermissionHandler
+import io.getstream.video.android.core.Call
import io.getstream.video.android.core.RingingState
import io.getstream.video.android.model.StreamCallId
+import io.getstream.video.android.model.User
+import kotlinx.coroutines.CoroutineScope
public interface NotificationHandler : NotificationPermissionHandler {
fun onRingingCall(callId: StreamCallId, callDisplayName: String)
@@ -29,16 +32,22 @@ public interface NotificationHandler : NotificationPermissionHandler {
fun getOngoingCallNotification(
callId: StreamCallId,
callDisplayName: String?,
- remoteParticipantCount: Int = 0,
isOutgoingCall: Boolean = false,
+ remoteParticipantCount: Int = 0,
): Notification?
fun getRingingCallNotification(
ringingState: RingingState,
callId: StreamCallId,
- callDisplayName: String,
+ callDisplayName: String?,
shouldHaveContentIntent: Boolean = true,
): Notification?
fun getSettingUpCallNotification(): Notification?
+ fun getNotificationUpdates(
+ coroutineScope: CoroutineScope,
+ call: Call,
+ localUser: User,
+ onUpdate: (Notification) -> Unit,
+ )
companion object {
const val ACTION_NOTIFICATION = "io.getstream.video.android.action.NOTIFICATION"
diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/NoOpNotificationHandler.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/NoOpNotificationHandler.kt
index a08a355e1a..fb35929e08 100644
--- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/NoOpNotificationHandler.kt
+++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/NoOpNotificationHandler.kt
@@ -17,9 +17,12 @@
package io.getstream.video.android.core.notifications.internal
import android.app.Notification
+import io.getstream.video.android.core.Call
import io.getstream.video.android.core.RingingState
import io.getstream.video.android.core.notifications.NotificationHandler
import io.getstream.video.android.model.StreamCallId
+import io.getstream.video.android.model.User
+import kotlinx.coroutines.CoroutineScope
internal object NoOpNotificationHandler : NotificationHandler {
override fun onRingingCall(callId: StreamCallId, callDisplayName: String) { /* NoOp */ }
@@ -29,16 +32,22 @@ internal object NoOpNotificationHandler : NotificationHandler {
override fun getOngoingCallNotification(
callId: StreamCallId,
callDisplayName: String?,
- remoteParticipantCount: Int,
isOutgoingCall: Boolean,
+ remoteParticipantCount: Int,
): Notification? = null
override fun getRingingCallNotification(
ringingState: RingingState,
callId: StreamCallId,
- callDisplayName: String,
+ callDisplayName: String?,
shouldHaveContentIntent: Boolean,
): Notification? = null
override fun getSettingUpCallNotification(): Notification? = null
+ override fun getNotificationUpdates(
+ coroutineScope: CoroutineScope,
+ call: Call,
+ localUser: User,
+ onUpdate: (Notification) -> Unit,
+ ) { /* NoOp */ }
override fun onPermissionDenied() { /* NoOp */ }
override fun onPermissionGranted() { /* NoOp */ }
diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt
index 9870a27c91..897e0868ab 100644
--- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt
+++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt
@@ -48,10 +48,6 @@ import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import org.openapitools.client.models.CallAcceptedEvent
import org.openapitools.client.models.CallEndedEvent
@@ -262,7 +258,7 @@ internal open class CallService : Service() {
first = streamVideo.getRingingCallNotification(
ringingState = RingingState.Incoming(),
callId = intentCallId,
- callDisplayName = intentCallDisplayName!!,
+ callDisplayName = intentCallDisplayName,
shouldHaveContentIntent = streamVideo.state.activeCall.value == null,
),
second = INCOMING_CALL_NOTIFICATION_ID,
@@ -421,7 +417,7 @@ internal open class CallService : Service() {
private fun observeCall(callId: StreamCallId, streamVideo: StreamVideoImpl) {
observeRingingState(callId, streamVideo)
observeCallEvents(callId, streamVideo)
- observeParticipants(callId, streamVideo)
+ observeNotificationUpdates(callId, streamVideo)
}
private fun observeRingingState(callId: StreamCallId, streamVideo: StreamVideoImpl) {
@@ -553,94 +549,18 @@ internal open class CallService : Service() {
}
}
- private fun observeParticipants(callId: StreamCallId, streamVideo: StreamVideoImpl) {
- serviceScope.launch {
- val call = streamVideo.call(callId.type, callId.id)
- var latestRemoteParticipantCount = -1
-
- // Monitor call state and remote participants
- combine(
- call.state.ringingState,
- call.state.members,
- call.state.remoteParticipants,
- ) { ringingState, members, remoteParticipants ->
- Triple(ringingState, members, remoteParticipants)
- }
- .distinctUntilChanged()
- .filter { it.first is RingingState.Active || it.first is RingingState.Outgoing }
- .collectLatest {
- val ringingState = it.first
- val members = it.second
- val remoteParticipants = it.third
-
- if (ringingState is RingingState.Outgoing) {
- val remoteMembersCount = members.size - 1
-
- val callDisplayName = if (remoteMembersCount != 1) {
- applicationContext.getString(
- R.string.stream_video_outgoing_call_notification_title,
- )
- } else {
- members.firstOrNull { member ->
- member.user.id != streamVideo.userId
- }?.user?.name ?: "Unknown"
- }
-
- val notification = streamVideo.getOngoingCallNotification(
- callId = callId,
- callDisplayName = callDisplayName,
- remoteParticipantCount = remoteMembersCount,
- isOutgoingCall = true,
- )
-
- notification?.let {
- startForegroundWithServiceType(
- callId.hashCode(),
- notification,
- TRIGGER_ONGOING_CALL,
- serviceType,
- )
- }
- } else if (ringingState is RingingState.Active) {
- // If number of remote participants increased or decreased
- if (remoteParticipants.size != latestRemoteParticipantCount) {
- latestRemoteParticipantCount = remoteParticipants.size
-
- val callDisplayName = if (remoteParticipants.isEmpty()) {
- // If no remote participants, get simple call notification title
- applicationContext.getString(
- R.string.stream_video_ongoing_call_notification_title,
- )
- } else {
- if (remoteParticipants.size > 1) {
- // If more than 1 remote participant, get group call notification title
- applicationContext.getString(
- R.string.stream_video_ongoing_group_call_notification_title,
- )
- } else {
- // If 1 remote participant, get the name of the remote participant
- remoteParticipants.firstOrNull()?.name?.value ?: "Unknown"
- }
- }
-
- // Use latest call display name in notification
- val notification = streamVideo.getOngoingCallNotification(
- callId = callId,
- callDisplayName = callDisplayName,
- remoteParticipantCount = remoteParticipants.size,
- )
-
- notification?.let {
- startForegroundWithServiceType(
- callId.hashCode(),
- notification,
- TRIGGER_ONGOING_CALL,
- serviceType,
- )
- }
- }
- }
- }
+ private fun observeNotificationUpdates(callId: StreamCallId, streamVideo: StreamVideoImpl) {
+ streamVideo.getNotificationUpdates(
+ serviceScope,
+ streamVideo.call(callId.type, callId.id),
+ streamVideo.user,
+ ) { notification ->
+ startForegroundWithServiceType(
+ callId.hashCode(),
+ notification,
+ TRIGGER_ONGOING_CALL,
+ serviceType,
+ )
}
}
diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/model/StreamCallId.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/model/StreamCallId.kt
index 85db14bd41..cd805daa7f 100644
--- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/model/StreamCallId.kt
+++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/model/StreamCallId.kt
@@ -79,4 +79,4 @@ public data class StreamCallId constructor(
public fun Intent.streamCallId(key: String): StreamCallId? =
IntentCompat.getParcelableExtra(this, key, StreamCallId::class.java)
-public fun Intent.streamCallDisplayName(key: String): String = this.getStringExtra(key) ?: "."
+public fun Intent.streamCallDisplayName(key: String): String? = this.getStringExtra(key)
diff --git a/stream-video-android-core/src/main/res/values/strings.xml b/stream-video-android-core/src/main/res/values/strings.xml
index a2abd9e451..ad9ee8f7ab 100644
--- a/stream-video-android-core/src/main/res/values/strings.xml
+++ b/stream-video-android-core/src/main/res/values/strings.xml
@@ -30,7 +30,7 @@
incoming_calls_low_priority
Incoming audio and video call alerts
Incoming audio and video call alerts
- Incoming call
+ Incoming call
outgoing_calls
Outgoing Calls
Outgoing call notifications