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 78171e6a4a..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 (Ljava/lang/String;Lio/getstream/video/android/model/StreamCallId;)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,7 +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 (Ljava/lang/String;Lio/getstream/video/android/model/StreamCallId;)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 c8db32af1a..4027d2d355 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 @@ -32,18 +32,26 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.CallStyle import androidx.core.app.NotificationManagerCompat import androidx.core.app.Person +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 import io.getstream.video.android.core.notifications.NotificationHandler.Companion.ACTION_MISSED_CALL import io.getstream.video.android.core.notifications.NotificationHandler.Companion.ACTION_NOTIFICATION -import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INCOMING_CALL_NOTIFICATION_ID 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) { @@ -132,10 +140,10 @@ public open class DefaultNotificationHandler( val endCallPendingIntent = intentResolver.searchEndCallPendingIntent(callId) if (outgoingCallPendingIntent != null && endCallPendingIntent != null) { - getOutgoingCallNotification( - outgoingCallPendingIntent, - endCallPendingIntent, + getOngoingCallNotification( + callId, callDisplayName, + isOutgoingCall = true, ) } else { logger.e { "Ringing call notification not shown, one of the intents is null." } @@ -183,7 +191,7 @@ public open class DefaultNotificationHandler( fullScreenPendingIntent: PendingIntent, acceptCallPendingIntent: PendingIntent, rejectCallPendingIntent: PendingIntent, - callDisplayName: String, + callerName: String?, shouldHaveContentIntent: Boolean, ): Notification { // if the app is in foreground then don't interrupt the user with a high priority @@ -231,12 +239,12 @@ public open class DefaultNotificationHandler( return getNotification { priority = NotificationCompat.PRIORITY_HIGH - setContentTitle( - application.getString(R.string.stream_video_incoming_call_notification_title), + setContentTitle(callerName) + setContentText( + application.getString(R.string.stream_video_incoming_call_notification_description), ) - setContentText(callDisplayName) setChannelId(channelId) - setOngoing(false) + setOngoing(true) setCategory(NotificationCompat.CATEGORY_CALL) setFullScreenIntent(fullScreenPendingIntent, true) if (shouldHaveContentIntent) { @@ -251,49 +259,7 @@ public open class DefaultNotificationHandler( setContentIntent(emptyIntent) setAutoCancel(false) } - addCallActions(acceptCallPendingIntent, rejectCallPendingIntent, callDisplayName) - } - } - - private fun getOutgoingCallNotification( - outgoingCallPendingIntent: PendingIntent, - endCallPendingIntent: PendingIntent, - callDisplayName: String, - ): Notification { - val channelId = application.getString( - R.string.stream_video_outgoing_call_notification_channel_id, - ) - maybeCreateChannel( - channelId = channelId, - context = application, - configure = { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - name = application.getString( - R.string.stream_video_outgoing_call_notification_channel_title, - ) - description = application.getString( - R.string.stream_video_outgoing_call_notification_channel_description, - ) - } - }, - ) - - return getNotification { - setContentTitle( - application.getString(R.string.stream_video_outgoing_call_notification_title), - ) - setContentText(callDisplayName) - setChannelId(channelId) - setOngoing(true) - setContentIntent(outgoingCallPendingIntent) - setCategory(NotificationCompat.CATEGORY_CALL) - addAction( - NotificationCompat.Action.Builder( - android.R.drawable.ic_menu_close_clear_cancel, - application.getString(R.string.stream_video_call_notification_action_cancel), - endCallPendingIntent, - ).build(), - ) + addCallActions(acceptCallPendingIntent, rejectCallPendingIntent, callerName) } } @@ -322,17 +288,30 @@ public open class DefaultNotificationHandler( } override fun getOngoingCallNotification( - callDisplayName: String?, callId: StreamCallId, + callDisplayName: String?, + isOutgoingCall: Boolean, + remoteParticipantCount: Int, ): Notification? { val notificationId = callId.hashCode() // Notification ID // Intents - val ongoingCallIntent = intentResolver.searchOngoingCallPendingIntent( - callId, - notificationId, - ) - val endCallIntent = intentResolver.searchEndCallPendingIntent(callId = callId) + val onClickIntent = if (isOutgoingCall) { + intentResolver.searchOutgoingCallPendingIntent( + callId, + notificationId, + ) + } else { + intentResolver.searchOngoingCallPendingIntent( + callId, + notificationId, + ) + } + val hangUpIntent = if (isOutgoingCall) { + intentResolver.searchRejectCallPendingIntent(callId) + } else { + intentResolver.searchEndCallPendingIntent(callId) + } // Channel preparation val ongoingCallsChannelId = application.getString( @@ -352,7 +331,7 @@ public open class DefaultNotificationHandler( }, ) - if (endCallIntent == null) { + if (hangUpIntent == null) { logger.e { "End call intent is null, not showing notification!" } return null } @@ -362,24 +341,115 @@ public open class DefaultNotificationHandler( .setSmallIcon(notificationIconRes) .also { // If the intent is configured, clicking the notification will return to the call - if (ongoingCallIntent != null) { - it.setContentIntent(ongoingCallIntent) + if (onClickIntent != null) { + it.setContentIntent(onClickIntent) } else { logger.w { "Ongoing intent is null click on the ongoing call notification will not work." } } } .setContentTitle( - application.getString(R.string.stream_video_ongoing_call_notification_title), + if (isOutgoingCall) { + application.getString(R.string.stream_video_outgoing_call_notification_title) + } else { + application.getString(R.string.stream_video_ongoing_call_notification_title) + }, ) .setContentText( application.getString(R.string.stream_video_ongoing_call_notification_description), ) .setAutoCancel(false) .setOngoing(true) - .addHangupAction(endCallIntent, callDisplayName ?: callId.toString()) + .addHangUpAction( + hangUpIntent, + 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, @@ -436,25 +506,6 @@ public open class DefaultNotificationHandler( } } - private fun showIncomingCallNotification( - fullScreenPendingIntent: PendingIntent, - acceptCallPendingIntent: PendingIntent, - rejectCallPendingIntent: PendingIntent, - callDisplayName: String, - notificationId: Int = INCOMING_CALL_NOTIFICATION_ID, - ) { - showNotification(notificationId) { - priority = NotificationCompat.PRIORITY_HIGH - setContentTitle("Incoming call") - setContentText(callDisplayName) - setOngoing(false) - setContentIntent(fullScreenPendingIntent) - setFullScreenIntent(fullScreenPendingIntent, true) - setCategory(NotificationCompat.CATEGORY_CALL) - addCallActions(acceptCallPendingIntent, rejectCallPendingIntent, callDisplayName) - } - } - @SuppressLint("MissingPermission") private fun showNotification( notificationId: Int, @@ -474,17 +525,37 @@ public open class DefaultNotificationHandler( .build() } - private fun NotificationCompat.Builder.addHangupAction( - rejectCallPendingIntent: PendingIntent, + private fun NotificationCompat.Builder.addHangUpAction( + hangUpIntent: PendingIntent, callDisplayName: String, + remoteParticipantCount: Int, ): NotificationCompat.Builder = apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { setStyle( CallStyle.forOngoingCall( Person.Builder() .setName(callDisplayName) + .apply { + if (remoteParticipantCount == 0) { + // Just one user in the call + setIcon( + IconCompat.createWithResource( + application, + R.drawable.stream_video_ic_user, + ), + ) + } else if (remoteParticipantCount > 1) { + // More than one user in the call + setIcon( + IconCompat.createWithResource( + application, + R.drawable.stream_video_ic_user_group, + ), + ) + } + } .build(), - rejectCallPendingIntent, + hangUpIntent, ), ) } else { @@ -492,7 +563,7 @@ public open class DefaultNotificationHandler( NotificationCompat.Action.Builder( null, application.getString(R.string.stream_video_call_notification_action_leave), - rejectCallPendingIntent, + hangUpIntent, ).build(), ) } @@ -501,13 +572,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 b9b7a41148..5926298082 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,23 +18,47 @@ 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) fun onMissedCall(callId: StreamCallId, callDisplayName: String) fun onNotification(callId: StreamCallId, callDisplayName: String) fun onLiveCall(callId: StreamCallId, callDisplayName: String) - fun getOngoingCallNotification(callDisplayName: String?, callId: StreamCallId): Notification? + fun getOngoingCallNotification( + callId: StreamCallId, + callDisplayName: String? = null, + isOutgoingCall: Boolean = false, + remoteParticipantCount: Int = 0, + ): Notification? fun getRingingCallNotification( ringingState: RingingState, callId: StreamCallId, - callDisplayName: String, + callDisplayName: String? = null, shouldHaveContentIntent: Boolean = true, ): Notification? fun getSettingUpCallNotification(): Notification? + /** + * Get subsequent updates to notifications. + * Initially, notifications are posted by one of the other methods, and then this method can be used to re-post them with updated content. + * + * @param coroutineScope Coroutine scope used for the updates. + * @param call The Stream call object. + * @param localUser The local Stream user. + * @param onUpdate Callback to be called when the notification is updated. + */ + fun getNotificationUpdates( + coroutineScope: CoroutineScope, + call: Call, + localUser: User, + onUpdate: (Notification) -> Unit, + ) + companion object { const val ACTION_NOTIFICATION = "io.getstream.video.android.action.NOTIFICATION" const val ACTION_MISSED_CALL = "io.getstream.video.android.action.MISSED_CALL" 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 f4fa89e3ad..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 */ } @@ -27,16 +30,24 @@ internal object NoOpNotificationHandler : NotificationHandler { override fun onNotification(callId: StreamCallId, callDisplayName: String) { /* NoOp */ } override fun onLiveCall(callId: StreamCallId, callDisplayName: String) { /* NoOp */ } override fun getOngoingCallNotification( - callDisplayName: String?, callId: StreamCallId, + callDisplayName: String?, + 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 9beb88a79f..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 @@ -248,8 +248,8 @@ internal open class CallService : Service() { val notificationData: Pair = when (trigger) { TRIGGER_ONGOING_CALL -> Pair( first = streamVideo.getOngoingCallNotification( - callDisplayName = intentCallDisplayName, callId = intentCallId, + callDisplayName = intentCallDisplayName, ), second = intentCallId.hashCode(), ) @@ -258,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, @@ -269,7 +269,7 @@ internal open class CallService : Service() { ringingState = RingingState.Outgoing(), callId = intentCallId, callDisplayName = getString( - R.string.stream_video_ongoing_call_notification_description, + R.string.stream_video_outgoing_call_notification_title, ), ), second = INCOMING_CALL_NOTIFICATION_ID, // Same for incoming and outgoing @@ -331,7 +331,7 @@ internal open class CallService : Service() { } else if (trigger == TRIGGER_OUTGOING_CALL) { if (mediaPlayer == null) mediaPlayer = MediaPlayer() } - observeCallState(intentCallId, streamVideo) + observeCall(intentCallId, streamVideo) registerToggleCameraBroadcastReceiver() return START_NOT_STICKY } @@ -414,8 +414,13 @@ internal open class CallService : Service() { } } - private fun observeCallState(callId: StreamCallId, streamVideo: StreamVideoImpl) { - // Ringing state + private fun observeCall(callId: StreamCallId, streamVideo: StreamVideoImpl) { + observeRingingState(callId, streamVideo) + observeCallEvents(callId, streamVideo) + observeNotificationUpdates(callId, streamVideo) + } + + private fun observeRingingState(callId: StreamCallId, streamVideo: StreamVideoImpl) { serviceScope.launch { val call = streamVideo.call(callId.type, callId.id) call.state.ringingState.collect { @@ -457,37 +462,6 @@ internal open class CallService : Service() { } } } - - // Call state - serviceScope.launch { - val call = streamVideo.call(callId.type, callId.id) - call.subscribe { event -> - logger.i { "Received event in service: $event" } - when (event) { - is CallAcceptedEvent -> { - handleIncomingCallAcceptedByMeOnAnotherDevice( - acceptedByUserId = event.user.id, - myUserId = streamVideo.userId, - callRingingState = call.state.ringingState.value, - ) - } - - is CallRejectedEvent -> { - handleIncomingCallRejectedByMeOrCaller( - rejectedByUserId = event.user.id, - myUserId = streamVideo.userId, - createdByUserId = call.state.createdBy.value?.id, - activeCallExists = streamVideo.state.activeCall.value != null, - ) - } - - is CallEndedEvent -> { - // When call ends for any reason - stopService() - } - } - } - } } private fun playCallSound(@RawRes sound: Int?) { @@ -524,6 +498,38 @@ internal open class CallService : Service() { } } + private fun observeCallEvents(callId: StreamCallId, streamVideo: StreamVideoImpl) { + serviceScope.launch { + val call = streamVideo.call(callId.type, callId.id) + call.subscribe { event -> + logger.i { "Received event in service: $event" } + when (event) { + is CallAcceptedEvent -> { + handleIncomingCallAcceptedByMeOnAnotherDevice( + acceptedByUserId = event.user.id, + myUserId = streamVideo.userId, + callRingingState = call.state.ringingState.value, + ) + } + + is CallRejectedEvent -> { + handleIncomingCallRejectedByMeOrCaller( + rejectedByUserId = event.user.id, + myUserId = streamVideo.userId, + createdByUserId = call.state.createdBy.value?.id, + activeCallExists = streamVideo.state.activeCall.value != null, + ) + } + + is CallEndedEvent -> { + // When call ends for any reason + stopService() + } + } + } + } + } + private fun handleIncomingCallAcceptedByMeOnAnotherDevice(acceptedByUserId: String, myUserId: String, callRingingState: RingingState) { // If accepted event was received, with event user being me, but current device is still ringing, it means the call was accepted on another device if (acceptedByUserId == myUserId && callRingingState is RingingState.Incoming) { @@ -543,6 +549,21 @@ internal open class CallService : Service() { } } + 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, + ) + } + } + private fun registerToggleCameraBroadcastReceiver() { serviceScope.launch { if (!isToggleCameraBroadcastReceiverRegistered) { 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/drawable/stream_video_ic_user.xml b/stream-video-android-core/src/main/res/drawable/stream_video_ic_user.xml new file mode 100644 index 0000000000..da19c18bca --- /dev/null +++ b/stream-video-android-core/src/main/res/drawable/stream_video_ic_user.xml @@ -0,0 +1,28 @@ + + + + + + diff --git a/stream-video-android-core/src/main/res/drawable/stream_video_ic_user_group.xml b/stream-video-android-core/src/main/res/drawable/stream_video_ic_user_group.xml new file mode 100644 index 0000000000..064403f90e --- /dev/null +++ b/stream-video-android-core/src/main/res/drawable/stream_video_ic_user_group.xml @@ -0,0 +1,34 @@ + + + + + + + + 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 ef39925f05..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,17 +30,18 @@ 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 Calling... - There is a call in progress, tap to go back to the call + Tap to go back to the call ongoing_calls Ongoing Calls Ongoing call notifications - Call in progress - There is a call in progress, tap to go back to the call + Call in Progress + Group Call in Progress + Tap to go back to the call call_setup Call Setup Temporary notifications used while setting up calls