Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve notifications #1183

Merged
merged 9 commits into from
Sep 20, 2024
7 changes: 5 additions & 2 deletions stream-video-android-core/api/stream-video-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,36 @@ 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?,
isOutgoingCall: Boolean = false,
remoteParticipantCount: Int = 0,
): Notification?
fun getRingingCallNotification(
ringingState: RingingState,
callId: StreamCallId,
callDisplayName: String,
callDisplayName: String?,
aleksandar-apostolov marked this conversation as resolved.
Show resolved Hide resolved
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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,37 @@
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 */ }
override fun onMissedCall(callId: StreamCallId, callDisplayName: String) { /* NoOp */ }
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 */ }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,8 @@ internal open class CallService : Service() {
val notificationData: Pair<Notification?, Int> = when (trigger) {
TRIGGER_ONGOING_CALL -> Pair(
first = streamVideo.getOngoingCallNotification(
callDisplayName = intentCallDisplayName,
callId = intentCallId,
callDisplayName = intentCallDisplayName,
),
second = intentCallId.hashCode(),
)
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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?) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2024 Stream.io Inc. All rights reserved.

Licensed under the Stream License;
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://github.com/GetStream/stream-video-android/blob/main/LICENSE

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M480,0a480,480 0 1,0 0,960a480,480 0 1,0 0,-960"
android:fillColor="#CCC"/>
<path
android:pathData="M480,480q-66,0 -113,-47t-47,-113q0,-66 47,-113t113,-47q66,0 113,47t47,113q0,66 -47,113t-113,47ZM160,720v-32q0,-34 17.5,-62.5T224,582q62,-31 126,-46.5T480,520q66,0 130,15.5T736,582q29,15 46.5,43.5T800,688v32q0,33 -23.5,56.5T720,800L240,800q-33,0 -56.5,-23.5T160,720Z"
android:fillColor="#434343"/>
</vector>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2024 Stream.io Inc. All rights reserved.

Licensed under the Stream License;
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://github.com/GetStream/stream-video-android/blob/main/LICENSE

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M480,0a480,480 0 1,0 0,960a480,480 0 1,0 0,-960"
android:fillColor="#CCC"/>
<group
android:scaleX="0.9"
android:scaleY="0.9"
android:translateX="52"
android:translateY="52">
<path
android:pathData="M84,696q-15,0 -25.5,-10.5T48,660v-21q0,-39 39,-63t105,-24q14,0 26,1t23,3q-12,18 -18.5,39.11Q216,616.23 216,638v58L84,696ZM300,696q-15,0 -25.5,-10.5T264,660v-22q0,-28 14.5,-50t43.5,-39q29,-17 69,-25t89.5,-8q49.5,0 89,8t68.5,25q29,16 43.5,38.69Q696,610.38 696,638v22q0,15 -10.5,25.5T660,696L300,696ZM744,696v-58q0,-22 -6.5,-42.5T719,556q9,-2 20.5,-3t28.5,-1q66,0 105,24t39,63v21q0,15 -10.5,25.5T876,696L744,696ZM192,504q-30,0 -51,-21t-21,-51q0,-30 21,-51t51,-21q30,0 51,21t21,51q0,30 -21,51t-51,21ZM768,504q-30,0 -51,-21t-21,-51q0,-30 21,-51t51,-21q30,0 51,21t21,51q0,30 -21,51t-51,21ZM480,468q-45,0 -76.5,-31.52T372,359.93q0,-44.93 31.52,-76.43 31.52,-31.5 76.55,-31.5 44.93,0 76.43,31.55Q588,315.1 588,360q0,45 -31.55,76.5T480,468Z"
android:fillColor="#434343" />
</group>
</vector>
9 changes: 5 additions & 4 deletions stream-video-android-core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,18 @@
<string name="stream_video_incoming_call_low_priority_notification_channel_id" translatable="false">incoming_calls_low_priority</string>
<string name="stream_video_incoming_call_low_priority_notification_channel_description">Incoming audio and video call alerts</string>
<string name="stream_video_incoming_call_notification_channel_description">Incoming audio and video call alerts</string>
<string name="stream_video_incoming_call_notification_title">Incoming call</string>
<string name="stream_video_incoming_call_notification_description">Incoming call</string>
<string name="stream_video_outgoing_call_notification_channel_id" translatable="false">outgoing_calls</string>
<string name="stream_video_outgoing_call_notification_channel_title">Outgoing Calls</string>
<string name="stream_video_outgoing_call_notification_channel_description">Outgoing call notifications</string>
<string name="stream_video_outgoing_call_notification_title" tools:ignore="TypographyEllipsis">Calling...</string>
<string name="stream_video_outgoing_call_notification_description">There is a call in progress, tap to go back to the call</string>
<string name="stream_video_outgoing_call_notification_description">Tap to go back to the call</string>
<string name="stream_video_ongoing_call_notification_channel_id" translatable="false">ongoing_calls</string>
<string name="stream_video_ongoing_call_notification_channel_title">Ongoing Calls</string>
<string name="stream_video_ongoing_call_notification_channel_description">Ongoing call notifications</string>
<string name="stream_video_ongoing_call_notification_title">Call in progress</string>
<string name="stream_video_ongoing_call_notification_description">There is a call in progress, tap to go back to the call</string>
<string name="stream_video_ongoing_call_notification_title">Call in Progress</string>
<string name="stream_video_ongoing_group_call_notification_title">Group Call in Progress</string>
<string name="stream_video_ongoing_call_notification_description">Tap to go back to the call</string>
<string name="stream_video_call_setup_notification_channel_id" translatable="false">call_setup</string>
<string name="stream_video_call_setup_notification_channel_title">Call Setup</string>
<string name="stream_video_call_setup_notification_channel_description">Temporary notifications used while setting up calls</string>
Expand Down
Loading