Skip to content

Commit 4c174b1

Browse files
Allow for in-depth override of notification building and updates while also updating the media notifications with MediaSession (#1453)
* Add media session to make the system slot it into the media slot * Correct updates of notifications * Add a possibility to intercept notification creation * Update livestream image * Spotless & Api dump * Keep backward compatible with old implementation * Add tests * Add a call service test * Spotless and API dump * Add the notification manager as API. * Add the notification manager as API. * Add small default notification handler test * Update correct notification id * Spotless & ApiDump * Add default text for livestream guest * Spotless & ApiDump * StreamDefaultNotificationHandler tests * add update notification tests * add get tests * Spotless & Apidump * Remove integration test base * Fix the test to not require integration * Spotless & Api dump * Promote foreground service vs justNotify * Low priority if screen is open * Add better description in the deprecated annotations * Add better description in the deprecated annotations
1 parent 3cd98eb commit 4c174b1

27 files changed

+5190
-211
lines changed

demo-app/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,9 @@ dependencies {
222222
implementation(project(":stream-video-android-filters-video"))
223223
compileOnly(project(":stream-video-android-previewdata"))
224224

225+
226+
implementation(libs.androidx.media.media)
227+
225228
// Noise Cancellation
226229
implementation(libs.stream.video.android.noise.cancellation)
227230

demo-app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
android:windowSoftInputMode="adjustPan">
4848
<intent-filter>
4949
<action android:name="android.intent.action.MAIN" />
50-
50+
<action android:name="io.getstream.video.android.action.MISSED_CALL" />
5151
<category android:name="android.intent.category.LAUNCHER" />
5252
</intent-filter>
5353
</activity>
120 KB
Loading

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

Lines changed: 160 additions & 29 deletions
Large diffs are not rendered by default.

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

Lines changed: 100 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package io.getstream.video.android.core.notifications
1818

19+
import android.Manifest
1920
import android.annotation.SuppressLint
2021
import android.app.ActivityManager
2122
import android.app.Application
@@ -25,8 +26,10 @@ import android.app.NotificationManager
2526
import android.app.PendingIntent
2627
import android.content.Context
2728
import android.content.Intent
29+
import android.content.pm.PackageManager
2830
import android.os.Build
2931
import androidx.annotation.DrawableRes
32+
import androidx.core.app.ActivityCompat
3033
import androidx.core.app.NotificationChannelCompat
3134
import androidx.core.app.NotificationCompat
3235
import androidx.core.app.NotificationCompat.CallStyle
@@ -42,7 +45,6 @@ import io.getstream.video.android.core.RingingState
4245
import io.getstream.video.android.core.StreamVideo
4346
import io.getstream.video.android.core.StreamVideoClient
4447
import io.getstream.video.android.core.notifications.NotificationHandler.Companion.ACTION_LIVE_CALL
45-
import io.getstream.video.android.core.notifications.NotificationHandler.Companion.ACTION_MISSED_CALL
4648
import io.getstream.video.android.core.notifications.NotificationHandler.Companion.ACTION_NOTIFICATION
4749
import io.getstream.video.android.core.notifications.internal.service.CallService
4850
import io.getstream.video.android.core.notifications.medianotifications.MediaNotificationConfig
@@ -51,12 +53,16 @@ import io.getstream.video.android.core.notifications.medianotifications.MediaNot
5153
import io.getstream.video.android.model.StreamCallId
5254
import io.getstream.video.android.model.User
5355
import kotlinx.coroutines.CoroutineScope
56+
import kotlinx.coroutines.coroutineScope
5457
import kotlinx.coroutines.flow.collectLatest
5558
import kotlinx.coroutines.flow.combine
5659
import kotlinx.coroutines.flow.distinctUntilChanged
5760
import kotlinx.coroutines.flow.filter
5861
import kotlinx.coroutines.launch
5962

63+
@Deprecated(
64+
message = "This class is deprecated. Use the notification interceptors instead.",
65+
)
6066
public open class DefaultNotificationHandler(
6167
private val application: Application,
6268
private val notificationPermissionHandler: NotificationPermissionHandler =
@@ -115,17 +121,15 @@ public open class DefaultNotificationHandler(
115121
override fun onMissedCall(callId: StreamCallId, callDisplayName: String) {
116122
logger.d { "[onMissedCall] #ringing; callId: ${callId.id}" }
117123
val notificationId = callId.hashCode()
118-
val intent = intentResolver.searchMissedCallPendingIntent(callId, notificationId)
119-
?: run {
120-
logger.e { "Couldn't find any activity for $ACTION_MISSED_CALL" }
121-
intentResolver.getDefaultPendingIntent()
122-
}
123-
StreamVideo.instance().state.ringingCall
124-
showMissedCallNotification(
125-
intent,
126-
callDisplayName,
127-
notificationId,
128-
)
124+
val notification = getMissedCallNotification(callId, callDisplayName)
125+
if (notification != null && ActivityCompat.checkSelfPermission(
126+
application,
127+
Manifest.permission.POST_NOTIFICATIONS,
128+
) == PackageManager.PERMISSION_GRANTED
129+
) {
130+
notificationManager.notify(notificationId, notification)
131+
return
132+
}
129133
}
130134

131135
override fun getRingingCallNotification(
@@ -170,6 +174,44 @@ public open class DefaultNotificationHandler(
170174
}
171175
}
172176

177+
override fun getMissedCallNotification(
178+
callId: StreamCallId,
179+
callDisplayName: String?,
180+
): Notification? {
181+
logger.d { "[getMissedCallNotification] callId: ${callId.id}, callDisplayName: $callDisplayName" }
182+
val notificationId = callId.hashCode()
183+
val intent = intentResolver.searchMissedCallPendingIntent(callId, notificationId)
184+
?: intentResolver.getDefaultPendingIntent()
185+
186+
val showAsHighPriority = !hideRingingNotificationInForeground || !isInForeground()
187+
val channelId = application.getString(
188+
if (showAsHighPriority) {
189+
R.string.stream_video_incoming_call_notification_channel_id
190+
} else {
191+
R.string.stream_video_incoming_call_low_priority_notification_channel_id
192+
},
193+
)
194+
195+
createIncomingCallChannel(channelId, showAsHighPriority)
196+
197+
// Build notification
198+
val notificationContent = callDisplayName?.let {
199+
application.getString(
200+
R.string.stream_video_missed_call_notification_description,
201+
it,
202+
)
203+
}
204+
return NotificationCompat.Builder(application, channelId)
205+
.setSmallIcon(R.drawable.stream_video_ic_call)
206+
.setChannelId(channelId)
207+
.setContentTitle(
208+
application.getString(R.string.stream_video_missed_call_notification_title),
209+
)
210+
.setContentText(notificationContent)
211+
.setContentIntent(intent).setAutoCancel(true)
212+
.build()
213+
}
214+
173215
override fun getSettingUpCallNotification(): Notification? {
174216
val channelId = application.getString(
175217
R.string.stream_video_call_setup_notification_channel_id,
@@ -203,6 +245,48 @@ public open class DefaultNotificationHandler(
203245
}
204246
}
205247

248+
override suspend fun onCallNotificationUpdate(call: Call): Notification? {
249+
coroutineScope {
250+
val localUser = StreamVideo.instance().state.user.value
251+
if (localUser != null) {
252+
getNotificationUpdates(
253+
coroutineScope = this,
254+
call = call,
255+
localUser = localUser,
256+
onUpdate = { notification ->
257+
logger.d { "[onCallNotificationUpdate] Updating notification: $notification" }
258+
if (ActivityCompat.checkSelfPermission(
259+
application,
260+
Manifest.permission.POST_NOTIFICATIONS,
261+
) == PackageManager.PERMISSION_GRANTED
262+
) {
263+
notificationManager.notify(
264+
StreamCallId.fromCallCid(call.cid).hashCode(),
265+
notification,
266+
)
267+
}
268+
},
269+
)
270+
}
271+
}
272+
return null
273+
}
274+
275+
override suspend fun updateOngoingCallNotification(
276+
call: Call,
277+
callDisplayName: String,
278+
): Notification? = null
279+
280+
override suspend fun updateOutgoingCallNotification(
281+
call: Call,
282+
callDisplayName: String?,
283+
): Notification? = null
284+
285+
override suspend fun updateIncomingCallNotification(
286+
call: Call,
287+
callDisplayName: String,
288+
): Notification? = null
289+
206290
override fun getIncomingCallNotification(
207291
fullScreenPendingIntent: PendingIntent,
208292
acceptCallPendingIntent: PendingIntent,
@@ -313,7 +397,8 @@ public open class DefaultNotificationHandler(
313397
remoteParticipantCount: Int,
314398
): Notification? {
315399
val client = (StreamVideo.instance() as StreamVideoClient)
316-
val mediaNotificationCallTypes = client.streamNotificationManager.notificationConfig.mediaNotificationCallTypes
400+
val mediaNotificationCallTypes =
401+
client.streamNotificationManager.notificationConfig.mediaNotificationCallTypes
317402
return if (mediaNotificationCallTypes.contains(callId.type)) {
318403
createMinimalMediaStyleNotification(
319404
callId,
@@ -470,7 +555,8 @@ public open class DefaultNotificationHandler(
470555
val currentRemoteParticipantCount = remoteParticipants.size
471556
// If number of remote participants increased or decreased
472557
if (currentRemoteParticipantCount != latestRemoteParticipantCount) {
473-
val isSameCase = currentRemoteParticipantCount > 1 && latestRemoteParticipantCount > 1
558+
val isSameCase =
559+
currentRemoteParticipantCount > 1 && latestRemoteParticipantCount > 1
474560
latestRemoteParticipantCount = currentRemoteParticipantCount
475561

476562
if (!isSameCase) {

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

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import io.getstream.video.android.core.notifications.NotificationHandler.Compani
2828
import io.getstream.video.android.core.notifications.internal.DismissNotificationActivity
2929
import io.getstream.video.android.model.StreamCallId
3030

31-
public class DefaultStreamIntentResolver(val context: Context) {
31+
public class DefaultStreamIntentResolver(val context: Context) : StreamIntentResolver {
3232

3333
private val logger by taggedLogger("IntentResolver")
3434

@@ -38,9 +38,9 @@ public class DefaultStreamIntentResolver(val context: Context) {
3838
* @param callId The call id from the incoming call.
3939
* @param notificationId the notification ID.
4040
*/
41-
fun searchIncomingCallPendingIntent(
41+
override fun searchIncomingCallPendingIntent(
4242
callId: StreamCallId,
43-
notificationId: Int = NotificationHandler.INCOMING_CALL_NOTIFICATION_ID,
43+
notificationId: Int,
4444
): PendingIntent? =
4545
searchActivityPendingIntent(
4646
Intent(NotificationHandler.ACTION_INCOMING_CALL),
@@ -55,9 +55,9 @@ public class DefaultStreamIntentResolver(val context: Context) {
5555
* @param callId the call id
5656
* @param notificationId the notification ID.
5757
*/
58-
fun searchOutgoingCallPendingIntent(
58+
override fun searchOutgoingCallPendingIntent(
5959
callId: StreamCallId,
60-
notificationId: Int = NotificationHandler.INCOMING_CALL_NOTIFICATION_ID,
60+
notificationId: Int,
6161
): PendingIntent? {
6262
return searchActivityPendingIntent(
6363
Intent(
@@ -74,7 +74,7 @@ public class DefaultStreamIntentResolver(val context: Context) {
7474
* @param callId The call id from the incoming call.
7575
* @param notificationId the notification ID.
7676
*/
77-
fun searchNotificationCallPendingIntent(
77+
override fun searchNotificationCallPendingIntent(
7878
callId: StreamCallId,
7979
notificationId: Int,
8080
): PendingIntent? =
@@ -90,7 +90,7 @@ public class DefaultStreamIntentResolver(val context: Context) {
9090
* @param callId The call id from the incoming call.
9191
* @param notificationId the notification ID.
9292
*/
93-
fun searchMissedCallPendingIntent(
93+
override fun searchMissedCallPendingIntent(
9494
callId: StreamCallId,
9595
notificationId: Int,
9696
): PendingIntent? =
@@ -100,7 +100,7 @@ public class DefaultStreamIntentResolver(val context: Context) {
100100
notificationId,
101101
)
102102

103-
fun getDefaultPendingIntent(): PendingIntent {
103+
override fun getDefaultPendingIntent(): PendingIntent {
104104
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)
105105
?: Intent(Intent.ACTION_MAIN).apply {
106106
setPackage(context.packageName)
@@ -121,7 +121,7 @@ public class DefaultStreamIntentResolver(val context: Context) {
121121
* @param callId The call id from the incoming call.
122122
* @param notificationId the notification ID.
123123
*/
124-
fun searchLiveCallPendingIntent(
124+
override fun searchLiveCallPendingIntent(
125125
callId: StreamCallId,
126126
notificationId: Int,
127127
): PendingIntent? =
@@ -138,9 +138,9 @@ public class DefaultStreamIntentResolver(val context: Context) {
138138
* @param notificationId the notification ID.
139139
* @return The [PendingIntent] which can trigger a component to consume accept call events.
140140
*/
141-
fun searchAcceptCallPendingIntent(
141+
override fun searchAcceptCallPendingIntent(
142142
callId: StreamCallId,
143-
notificationId: Int = NotificationHandler.INCOMING_CALL_NOTIFICATION_ID,
143+
notificationId: Int,
144144
): PendingIntent? =
145145
searchActivityPendingIntent(
146146
Intent(NotificationHandler.ACTION_ACCEPT_CALL),
@@ -155,7 +155,7 @@ public class DefaultStreamIntentResolver(val context: Context) {
155155
* @param callId The ID of the call.
156156
* @return The [PendingIntent] which can trigger a component to consume the call rejection event.
157157
*/
158-
fun searchRejectCallPendingIntent(
158+
override fun searchRejectCallPendingIntent(
159159
callId: StreamCallId,
160160
): PendingIntent? = searchBroadcastPendingIntent(Intent(ACTION_REJECT_CALL), callId)
161161

@@ -166,7 +166,7 @@ public class DefaultStreamIntentResolver(val context: Context) {
166166
* @param callId The ID of the call.
167167
* @return The [PendingIntent] which can trigger a component to consume the call rejection event.
168168
*/
169-
fun searchEndCallPendingIntent(
169+
override fun searchEndCallPendingIntent(
170170
callId: StreamCallId,
171171
): PendingIntent? = searchBroadcastPendingIntent(
172172
Intent(NotificationHandler.ACTION_LEAVE_CALL),
@@ -179,7 +179,10 @@ public class DefaultStreamIntentResolver(val context: Context) {
179179
* @param callId the call id
180180
* @param notificationId the notification ID.
181181
*/
182-
fun searchOngoingCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? {
182+
override fun searchOngoingCallPendingIntent(
183+
callId: StreamCallId,
184+
notificationId: Int,
185+
): PendingIntent? {
183186
val intent = Intent(ACTION_ONGOING_CALL)
184187
intent.putExtra(INTENT_EXTRA_CALL_CID, callId.cid)
185188
return searchActivityPendingIntent(

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
package io.getstream.video.android.core.notifications
1818

1919
import io.getstream.android.push.PushDeviceGenerator
20+
import io.getstream.video.android.core.Call
2021
import io.getstream.video.android.core.call.CallType
22+
import io.getstream.video.android.core.internal.ExperimentalStreamVideoApi
2123
import io.getstream.video.android.core.notifications.internal.NoOpNotificationHandler
24+
import kotlinx.coroutines.flow.Flow
2225

2326
public data class NotificationConfig(
2427
val pushDeviceGenerators: List<PushDeviceGenerator> = emptyList(),
@@ -43,4 +46,10 @@ public data class NotificationConfig(
4346
* Allows support for custom user-defined call types.
4447
*/
4548
val mediaNotificationCallTypes: Set<String> = hashSetOf(CallType.Livestream.name),
49+
/**
50+
* When notification updates are triggered, the update is based on observing internal call state.
51+
* This parameters enables customization on which state flows will trigger a notification update.
52+
*/
53+
@ExperimentalStreamVideoApi
54+
val notificationUpdateTriggers: (Call) -> Flow<*>? = { _ -> null },
4655
)

0 commit comments

Comments
 (0)