@capgo/capacitor-stream-call 0.0.87 → 0.0.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -73,6 +73,8 @@ dependencies {
73
73
  implementation "androidx.compose.foundation:foundation:$compose_version"
74
74
  implementation "androidx.compose.runtime:runtime:$compose_version"
75
75
  implementation "androidx.compose.material3:material3:1.3.2"
76
+ implementation 'androidx.media:media:1.7.0'
77
+
76
78
 
77
79
  // Stream dependencies
78
80
  implementation("io.getstream:stream-video-android-ui-compose:1.9.2")
@@ -0,0 +1,161 @@
1
+ package ee.forgr.capacitor.streamcall
2
+
3
+ import android.app.Application
4
+ import android.app.PendingIntent
5
+ import android.content.ComponentName
6
+ import android.content.Intent
7
+ import android.content.pm.ResolveInfo
8
+ import io.getstream.log.taggedLogger
9
+ import io.getstream.video.android.core.notifications.NotificationHandler
10
+ import io.getstream.video.android.core.notifications.StreamIntentResolver
11
+ import io.getstream.video.android.model.StreamCallId
12
+
13
+ class CustomStreamIntentResolver(private val context: Application) : StreamIntentResolver {
14
+
15
+ private val logger by taggedLogger("CustomIntentResolver")
16
+ private val PENDING_INTENT_FLAG = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
17
+
18
+ override fun searchIncomingCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? {
19
+ val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
20
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
21
+ putExtra("callCid", callId.cid)
22
+ putExtra("action", "accept")
23
+ action = "io.getstream.video.android.action.ACCEPT_CALL"
24
+ }
25
+
26
+ return PendingIntent.getActivity(
27
+ context,
28
+ callId.cid.hashCode(),
29
+ launchIntent,
30
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
31
+ )
32
+ }
33
+
34
+ override fun searchOutgoingCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? {
35
+ // For outgoing calls, create a specific intent that only opens webview when user taps
36
+ val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
37
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
38
+ putExtra("callCid", callId.cid)
39
+ putExtra("action", "outgoing_call_tap") // Different action to distinguish from automatic events
40
+ putExtra("openWebview", true)
41
+ putExtra("fromNotification", true)
42
+ putExtra("userTapped", true) // Explicitly mark this as user-initiated
43
+ action = "ee.forgr.capacitor.streamcall.OUTGOING_CALL_TAP.${callId.cid}"
44
+ }
45
+
46
+ return PendingIntent.getActivity(
47
+ context,
48
+ callId.cid.hashCode(),
49
+ launchIntent,
50
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
51
+ )
52
+ }
53
+
54
+ override fun searchNotificationCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? =
55
+ searchActivityPendingIntent(Intent(NotificationHandler.ACTION_NOTIFICATION), callId, notificationId)
56
+
57
+ override fun searchMissedCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? =
58
+ searchActivityPendingIntent(Intent(NotificationHandler.ACTION_MISSED_CALL), callId, notificationId)
59
+
60
+ override fun getDefaultPendingIntent(): PendingIntent {
61
+ val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)
62
+ ?: Intent(Intent.ACTION_MAIN).apply {
63
+ setPackage(context.packageName)
64
+ addCategory(Intent.CATEGORY_LAUNCHER)
65
+ }
66
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
67
+ return PendingIntent.getActivity(
68
+ context,
69
+ 0,
70
+ intent,
71
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
72
+ )
73
+ }
74
+
75
+ override fun searchLiveCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? =
76
+ searchActivityPendingIntent(Intent(NotificationHandler.ACTION_LIVE_CALL), callId, notificationId)
77
+
78
+ override fun searchAcceptCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? {
79
+ val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
80
+ action = NotificationHandler.ACTION_ACCEPT_CALL
81
+ putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
82
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
83
+ }
84
+
85
+ return PendingIntent.getActivity(
86
+ context,
87
+ callId.cid.hashCode() + 10,
88
+ launchIntent,
89
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
90
+ )
91
+ }
92
+
93
+ override fun searchRejectCallPendingIntent(callId: StreamCallId): PendingIntent? =
94
+ searchBroadcastPendingIntent(Intent(NotificationHandler.ACTION_REJECT_CALL), callId)
95
+
96
+ override fun searchEndCallPendingIntent(callId: StreamCallId): PendingIntent? {
97
+ val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
98
+ action = NotificationHandler.ACTION_LEAVE_CALL
99
+ putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
100
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
101
+ }
102
+
103
+ return PendingIntent.getActivity(
104
+ context,
105
+ callId.cid.hashCode() + 30,
106
+ launchIntent,
107
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
108
+ )
109
+ }
110
+
111
+ override fun searchOngoingCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? {
112
+ val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
113
+ action = NotificationHandler.ACTION_ONGOING_CALL
114
+ putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
115
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
116
+ }
117
+
118
+ return PendingIntent.getActivity(
119
+ context,
120
+ callId.cid.hashCode() + 20,
121
+ launchIntent,
122
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
123
+ )
124
+ }
125
+
126
+ private fun searchBroadcastPendingIntent(baseIntent: Intent, callId: StreamCallId): PendingIntent? =
127
+ searchResolveInfo { context.packageManager.queryBroadcastReceivers(baseIntent, 0) }?.let {
128
+ getBroadcastForIntent(baseIntent, it, callId)
129
+ }
130
+
131
+ private fun searchActivityPendingIntent(baseIntent: Intent, callId: StreamCallId, notificationId: Int): PendingIntent? =
132
+ searchResolveInfo { context.packageManager.queryIntentActivities(baseIntent, 0) }?.let {
133
+ getActivityForIntent(baseIntent, it, callId, notificationId)
134
+ }
135
+
136
+ private fun searchResolveInfo(availableComponents: () -> List<ResolveInfo>): ResolveInfo? =
137
+ availableComponents()
138
+ .filter { it.activityInfo.packageName == context.packageName }
139
+ .maxByOrNull { it.priority }
140
+
141
+ private fun getActivityForIntent(baseIntent: Intent, resolveInfo: ResolveInfo, callId: StreamCallId, notificationId: Int, flags: Int = PENDING_INTENT_FLAG): PendingIntent {
142
+ return PendingIntent.getActivity(
143
+ context,
144
+ notificationId,
145
+ buildComponentIntent(baseIntent, resolveInfo, callId),
146
+ flags
147
+ )
148
+ }
149
+
150
+ private fun getBroadcastForIntent(baseIntent: Intent, resolveInfo: ResolveInfo, callId: StreamCallId, flags: Int = PENDING_INTENT_FLAG): PendingIntent {
151
+ return PendingIntent.getBroadcast(context, 0, buildComponentIntent(baseIntent, resolveInfo, callId), flags)
152
+ }
153
+
154
+ private fun buildComponentIntent(baseIntent: Intent, resolveInfo: ResolveInfo, callId: StreamCallId): Intent {
155
+ return Intent(baseIntent).apply {
156
+ component = ComponentName(resolveInfo.activityInfo.applicationInfo.packageName, resolveInfo.activityInfo.name)
157
+ putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
158
+ putExtra(NotificationHandler.INTENT_EXTRA_NOTIFICATION_ID, NotificationHandler.INCOMING_CALL_NOTIFICATION_ID)
159
+ }
160
+ }
161
+ }
@@ -73,6 +73,10 @@ import io.getstream.video.android.core.events.ParticipantLeftEvent
73
73
  import io.getstream.video.android.core.internal.InternalStreamVideoApi
74
74
  import io.getstream.video.android.core.notifications.NotificationConfig
75
75
  import io.getstream.video.android.core.notifications.NotificationHandler
76
+ import io.getstream.video.android.core.notifications.handlers.CompatibilityStreamNotificationHandler
77
+ import io.getstream.video.android.core.notifications.handlers.StreamNotificationBuilderInterceptors
78
+ import androidx.core.app.NotificationCompat
79
+ import android.app.PendingIntent
76
80
  import io.getstream.video.android.core.sounds.RingingConfig
77
81
  import io.getstream.video.android.core.sounds.toSounds
78
82
  import io.getstream.video.android.model.Device
@@ -316,6 +320,12 @@ class StreamCallPlugin : Plugin() {
316
320
  Log.e("StreamCallPlugin", "handleOnNewIntent: ACCEPT_CALL - Call object is null for cid: $cid")
317
321
  }
318
322
  }
323
+ } else if (action === NotificationHandler.ACTION_LEAVE_CALL) {
324
+ Log.d("StreamCallPlugin", "handleOnNewIntent: Matched LEAVE_CALL action")
325
+ kotlinx.coroutines.GlobalScope.launch {
326
+ val success = _endCall()
327
+ Log.d("StreamCallPlugin", "handleOnNewIntent: LEAVE_CALL - End call result: $success")
328
+ }
319
329
  }
320
330
  // Log the intent information
321
331
  Log.d("StreamCallPlugin", "New Intent - Action: $action")
@@ -567,47 +577,6 @@ class StreamCallPlugin : Plugin() {
567
577
  // unsafe cast, add better handling
568
578
  val application = contextToUse.applicationContext as Application
569
579
  Log.d("StreamCallPlugin", "No existing StreamVideo singleton client, creating new one")
570
- val notificationHandler = CustomNotificationHandler(
571
- application = application,
572
- endCall = { callId ->
573
- val activeCall = streamVideoClient?.call(callId.type, callId.id)
574
-
575
- kotlinx.coroutines.GlobalScope.launch {
576
- try {
577
- Log.i(
578
- "StreamCallPlugin",
579
- "Attempt to endCallRaw, activeCall == null: ${activeCall == null}",
580
- )
581
- activeCall?.let { endCallRaw(it) }
582
- } catch (e: Exception) {
583
- Log.e(
584
- "StreamCallPlugin",
585
- "Error ending after missed call notif action",
586
- e
587
- )
588
- }
589
- }
590
- },
591
- incomingCall = {
592
- if (this.savedContext != null && initializationTime != 0L) {
593
- val contextCreatedAt = initializationTime
594
- val now = System.currentTimeMillis()
595
- val isWithinOneSecond = (now - contextCreatedAt) <= 1000L
596
-
597
- Log.i(
598
- "StreamCallPlugin",
599
- "Time between context creation and activity created (incoming call notif): ${now - contextCreatedAt}"
600
- )
601
- if (isWithinOneSecond && !bootedToHandleCall) {
602
- Log.i(
603
- "StreamCallPlugin",
604
- "Notification incomingCall received less than 1 second after the creation of streamVideoSDK. Booted FOR SURE in order to handle the notification"
605
- )
606
- }
607
- }
608
- }
609
- )
610
-
611
580
  val notificationConfig = NotificationConfig(
612
581
  pushDeviceGenerators = listOf(
613
582
  FirebasePushDeviceGenerator(
@@ -616,7 +585,23 @@ class StreamCallPlugin : Plugin() {
616
585
  )
617
586
  ),
618
587
  requestPermissionOnAppLaunch = { true },
619
- notificationHandler = notificationHandler,
588
+ notificationHandler = CompatibilityStreamNotificationHandler(
589
+ application = contextToUse as Application,
590
+ intentResolver = CustomStreamIntentResolver(contextToUse),
591
+ initialNotificationBuilderInterceptor = object : StreamNotificationBuilderInterceptors() {
592
+ override fun onBuildIncomingCallNotification(
593
+ builder: NotificationCompat.Builder,
594
+ fullScreenPendingIntent: PendingIntent,
595
+ acceptCallPendingIntent: PendingIntent,
596
+ rejectCallPendingIntent: PendingIntent,
597
+ callerName: String?,
598
+ shouldHaveContentIntent: Boolean
599
+ ): NotificationCompat.Builder {
600
+ return builder.setContentIntent(fullScreenPendingIntent)
601
+ .setFullScreenIntent(fullScreenPendingIntent, true)
602
+ }
603
+ }
604
+ )
620
605
  )
621
606
 
622
607
  val soundsConfig = incomingOnlyRingingConfig()
@@ -1976,28 +1961,41 @@ class StreamCallPlugin : Plugin() {
1976
1961
  }
1977
1962
 
1978
1963
  @OptIn(DelicateCoroutinesApi::class)
1964
+ private suspend fun _endCall(): Boolean {
1965
+ val activeCall = streamVideoClient?.state?.activeCall?.value
1966
+ val ringingCall = streamVideoClient?.state?.ringingCall?.value
1967
+
1968
+ val callToEnd = activeCall ?: ringingCall
1969
+
1970
+ if (callToEnd == null) {
1971
+ Log.w("StreamCallPlugin", "Attempted to end call but no active or ringing call found")
1972
+ return false
1973
+ }
1974
+
1975
+ Log.d("StreamCallPlugin", "Ending call: activeCall=${activeCall?.id}, ringingCall=${ringingCall?.id}, callToEnd=${callToEnd.id}")
1976
+
1977
+ return try {
1978
+ endCallRaw(callToEnd)
1979
+ true
1980
+ } catch (e: Exception) {
1981
+ Log.e("StreamCallPlugin", "Error ending call: ${e.message}", e)
1982
+ false
1983
+ }
1984
+ }
1985
+
1979
1986
  @PluginMethod
1980
1987
  fun endCall(call: PluginCall) {
1981
1988
  try {
1982
- val activeCall = streamVideoClient?.state?.activeCall?.value
1983
- val ringingCall = streamVideoClient?.state?.ringingCall?.value
1984
-
1985
- val callToEnd = activeCall ?: ringingCall
1986
-
1987
- if (callToEnd == null) {
1988
- Log.w("StreamCallPlugin", "Attempted to end call but no active or ringing call found")
1989
- call.reject("No active call to end")
1990
- return
1991
- }
1992
-
1993
- Log.d("StreamCallPlugin", "Ending call: activeCall=${activeCall?.id}, ringingCall=${ringingCall?.id}, callToEnd=${callToEnd.id}")
1994
-
1995
1989
  kotlinx.coroutines.GlobalScope.launch {
1996
1990
  try {
1997
- endCallRaw(callToEnd)
1998
- call.resolve(JSObject().apply {
1999
- put("success", true)
2000
- })
1991
+ val success = _endCall()
1992
+ if (success) {
1993
+ call.resolve(JSObject().apply {
1994
+ put("success", true)
1995
+ })
1996
+ } else {
1997
+ call.reject("No active call to end")
1998
+ }
2001
1999
  } catch (e: Exception) {
2002
2000
  Log.e("StreamCallPlugin", "Error ending call: ${e.message}")
2003
2001
  call.reject("Failed to end call: ${e.message}")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-stream-call",
3
- "version": "0.0.87",
3
+ "version": "0.0.88",
4
4
  "description": "Uses the https://getstream.io/ SDK to implement calling in Capacitor",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",
@@ -1,323 +0,0 @@
1
- package ee.forgr.capacitor.streamcall
2
-
3
- import android.app.Application
4
- import android.app.Notification
5
- import android.app.NotificationManager
6
- import android.app.PendingIntent
7
- import android.content.Intent
8
- import android.media.RingtoneManager
9
- import android.os.Build
10
- import android.util.Log
11
- import androidx.core.app.NotificationCompat
12
- import io.getstream.video.android.core.RingingState
13
- import io.getstream.video.android.core.notifications.DefaultNotificationHandler
14
- import io.getstream.video.android.core.notifications.NotificationHandler
15
- import io.getstream.video.android.model.StreamCallId
16
- import io.getstream.video.android.model.streamCallId
17
- import io.getstream.video.android.core.R
18
-
19
- // declare "incoming_calls_custom" as a constant
20
- const val INCOMING_CALLS_CUSTOM = "incoming_calls_custom"
21
-
22
- class CustomNotificationHandler(
23
- val application: Application,
24
- private val endCall: (callId: StreamCallId) -> Unit = {},
25
- private val incomingCall: () -> Unit = {}
26
- ) : DefaultNotificationHandler(application, hideRingingNotificationInForeground = false) {
27
- companion object {
28
- private const val PREFS_NAME = "StreamCallPrefs"
29
- private const val KEY_NOTIFICATION_TIME = "notification_creation_time"
30
- }
31
- private var allowSound = true
32
-
33
- override fun getRingingCallNotification(
34
- ringingState: RingingState,
35
- callId: StreamCallId,
36
- callDisplayName: String?,
37
- shouldHaveContentIntent: Boolean,
38
- ): Notification? {
39
- Log.d("CustomNotificationHandler", "getRingingCallNotification called: ringingState=$ringingState, callId=$callId, callDisplayName=$callDisplayName, shouldHaveContentIntent=$shouldHaveContentIntent")
40
- return if (ringingState is RingingState.Incoming) {
41
- // Note: we create our own fullScreenPendingIntent later based on acceptCallPendingIntent
42
-
43
- // Get the main launch intent for the application
44
- val launchIntent = application.packageManager.getLaunchIntentForPackage(application.packageName)
45
- var targetComponent: android.content.ComponentName? = null
46
- if (launchIntent != null) {
47
- targetComponent = launchIntent.component
48
- Log.d("CustomNotificationHandler", "Derived launch component: ${targetComponent?.flattenToString()}")
49
- } else {
50
- Log.e("CustomNotificationHandler", "Could not get launch intent for package: ${application.packageName}. This is problematic for creating explicit intents.")
51
- }
52
-
53
- // Intent to simply bring the app to foreground and show incoming-call UI (no auto accept)
54
- val incomingIntentAction = "io.getstream.video.android.action.INCOMING_CALL"
55
- val incomingCallIntent = Intent(incomingIntentAction)
56
- .putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
57
- .setPackage(application.packageName)
58
- if (targetComponent != null) incomingCallIntent.component = targetComponent
59
- incomingCallIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
60
-
61
- // Use the app's MainActivity intent so webview loads; user sees app UI
62
- val requestCodeFull = callId.cid.hashCode()
63
- val fullScreenPendingIntent = PendingIntent.getActivity(
64
- application,
65
- requestCodeFull,
66
- incomingCallIntent,
67
- PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
68
- )
69
-
70
- val acceptCallAction = NotificationHandler.ACTION_ACCEPT_CALL
71
- val acceptCallIntent = Intent(acceptCallAction).apply {
72
- putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
73
- // Explicitly target our manifest-declared receiver
74
- setClass(application, AcceptCallReceiver::class.java)
75
- }
76
-
77
- Log.d("CustomNotificationHandler", "Constructed Accept Call Intent for PI: action=${acceptCallIntent.action}, cid=${acceptCallIntent.streamCallId(NotificationHandler.INTENT_EXTRA_CALL_CID)}, component=${acceptCallIntent.component}")
78
-
79
- val requestCodeAccept = callId.cid.hashCode() + 1 // Unique request code for the PendingIntent with offset to avoid collisions
80
- val acceptCallPendingIntent = createAcceptCallPendingIntent(callId, requestCodeAccept, acceptCallIntent)
81
- Log.d("CustomNotificationHandler", "Created Accept Call PendingIntent with requestCode: $requestCodeAccept")
82
-
83
- val rejectCallPendingIntent = intentResolver.searchRejectCallPendingIntent(callId) // Keep using resolver for reject for now, or change it too if needed
84
-
85
- Log.d("CustomNotificationHandler", "Full Screen PI: $fullScreenPendingIntent")
86
- Log.d("CustomNotificationHandler", "Custom Accept Call PI: $acceptCallPendingIntent")
87
- Log.d("CustomNotificationHandler", "Resolver Reject Call PI: $rejectCallPendingIntent")
88
-
89
- if (fullScreenPendingIntent != null && acceptCallPendingIntent != null && rejectCallPendingIntent != null) {
90
- customGetIncomingCallNotification(
91
- fullScreenPendingIntent,
92
- acceptCallPendingIntent,
93
- rejectCallPendingIntent,
94
- callDisplayName,
95
- shouldHaveContentIntent,
96
- callId
97
- )
98
- } else {
99
- Log.e("CustomNotificationHandler", "Ringing call notification not shown, one of the intents is null.")
100
- null
101
- }
102
- } else if (ringingState is RingingState.Outgoing) {
103
- val outgoingCallPendingIntent = intentResolver.searchOutgoingCallPendingIntent(callId)
104
- val endCallPendingIntent = intentResolver.searchEndCallPendingIntent(callId)
105
-
106
- if (outgoingCallPendingIntent != null && endCallPendingIntent != null) {
107
- getOngoingCallNotification(
108
- callId,
109
- callDisplayName,
110
- isOutgoingCall = true,
111
- )
112
- } else {
113
- Log.e("CustomNotificationHandler", "Ringing call notification not shown, one of the intents is null.")
114
- null
115
- }
116
- } else {
117
- null
118
- }
119
- }
120
-
121
- private fun createAcceptCallPendingIntent(
122
- callId: StreamCallId,
123
- requestCode: Int,
124
- acceptCallIntent: Intent
125
- ): PendingIntent? {
126
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
127
- val launchIntent = application.packageManager.getLaunchIntentForPackage(application.packageName)
128
- if (launchIntent != null) {
129
- launchIntent.action = NotificationHandler.ACTION_ACCEPT_CALL
130
- launchIntent.putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
131
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
132
- PendingIntent.getActivity(
133
- application,
134
- requestCode,
135
- launchIntent,
136
- PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
137
- )
138
- } else {
139
- Log.e("CustomNotificationHandler", "Could not get launch intent for package to create Accept PI.")
140
- null
141
- }
142
- } else {
143
- PendingIntent.getBroadcast(
144
- application,
145
- requestCode,
146
- acceptCallIntent,
147
- PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
148
- )
149
- }
150
- }
151
-
152
- fun customGetIncomingCallNotification(
153
- fullScreenPendingIntent: PendingIntent,
154
- acceptCallPendingIntent: PendingIntent,
155
- rejectCallPendingIntent: PendingIntent,
156
- callerName: String?,
157
- shouldHaveContentIntent: Boolean,
158
- callId: StreamCallId
159
- ): Notification {
160
- Log.d("CustomNotificationHandler", "customGetIncomingCallNotification called: callerName=$callerName, callId=$callId")
161
- customCreateIncomingCallChannel()
162
- // Always use the provided acceptCallPendingIntent (created with getActivity) so that
163
- // the app process is started and MainActivity receives the ACCEPT_CALL action even
164
- // when the app has been killed.
165
- return buildNotification(
166
- fullScreenPendingIntent,
167
- acceptCallPendingIntent,
168
- rejectCallPendingIntent,
169
- callerName,
170
- shouldHaveContentIntent,
171
- INCOMING_CALLS_CUSTOM,
172
- true // Include sound
173
- )
174
- }
175
-
176
- private fun buildNotification(
177
- fullScreenPendingIntent: PendingIntent,
178
- acceptCallPendingIntent: PendingIntent,
179
- rejectCallPendingIntent: PendingIntent,
180
- callerName: String?,
181
- shouldHaveContentIntent: Boolean,
182
- channelId: String,
183
- includeSound: Boolean
184
- ): Notification {
185
- Log.d("CustomNotificationHandler", "buildNotification called: callerName=$callerName, channelId=$channelId, includeSound=$includeSound")
186
- return getNotification {
187
- priority = NotificationCompat.PRIORITY_HIGH
188
- setContentTitle(callerName)
189
- setContentText("Incoming call")
190
- setChannelId(channelId)
191
- setOngoing(true)
192
- setAutoCancel(false)
193
- setCategory(NotificationCompat.CATEGORY_CALL)
194
-
195
- // Clear all defaults first
196
- setDefaults(0)
197
-
198
- if (includeSound && allowSound) {
199
- setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE))
200
- setDefaults(NotificationCompat.DEFAULT_VIBRATE or NotificationCompat.DEFAULT_LIGHTS)
201
- } else {
202
- setSound(null)
203
- setDefaults(NotificationCompat.DEFAULT_VIBRATE or NotificationCompat.DEFAULT_LIGHTS)
204
- }
205
-
206
- // setVibrate(longArrayOf(0, 1000, 500, 1000))
207
- setLights(0xFF0000FF.toInt(), 1000, 1000)
208
- setFullScreenIntent(fullScreenPendingIntent, true)
209
- if (shouldHaveContentIntent) {
210
- setContentIntent(fullScreenPendingIntent)
211
- } else {
212
- val emptyIntent = PendingIntent.getActivity(
213
- application,
214
- 0,
215
- Intent(),
216
- PendingIntent.FLAG_IMMUTABLE,
217
- )
218
- setContentIntent(emptyIntent)
219
- }
220
- addCallActions(acceptCallPendingIntent, rejectCallPendingIntent, callerName)
221
- }.apply {
222
- // flags = flags or NotificationCompat.FLAG_ONGOING_EVENT
223
- }
224
- }
225
-
226
- override fun onMissedCall(callId: StreamCallId, callDisplayName: String) {
227
- Log.d("CustomNotificationHandler", "onMissedCall called: callId=$callId, callDisplayName=$callDisplayName")
228
- endCall(callId)
229
- super.onMissedCall(callId, callDisplayName)
230
- }
231
-
232
- override fun getOngoingCallNotification(
233
- callId: StreamCallId,
234
- callDisplayName: String?,
235
- isOutgoingCall: Boolean,
236
- remoteParticipantCount: Int
237
- ): Notification? {
238
- Log.d("CustomNotificationHandler", "getOngoingCallNotification called: callId=$callId, isOutgoing=$isOutgoingCall, participants=$remoteParticipantCount")
239
- createOngoingCallChannel()
240
-
241
- val launchIntent = application.packageManager.getLaunchIntentForPackage(application.packageName)
242
- val contentIntent = if (launchIntent != null) {
243
- launchIntent.putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
244
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
245
- PendingIntent.getActivity(
246
- application,
247
- callId.cid.hashCode(),
248
- launchIntent,
249
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
250
- )
251
- } else {
252
- Log.e("CustomNotificationHandler", "Could not get launch intent for package: ${application.packageName}. Ongoing call notification will not open the app.")
253
- null
254
- }
255
-
256
- return getNotification {
257
- setContentTitle(callDisplayName ?: "Ongoing Call")
258
- setContentText("Tap to return to the call")
259
- setSmallIcon(R.drawable.stream_video_ic_call)
260
- setChannelId("ongoing_calls")
261
- setOngoing(true)
262
- setAutoCancel(false)
263
- setCategory(NotificationCompat.CATEGORY_CALL)
264
- setDefaults(0)
265
- if (contentIntent != null) {
266
- setContentIntent(contentIntent)
267
- }
268
- }
269
- }
270
-
271
- private fun customCreateIncomingCallChannel() {
272
- Log.d("CustomNotificationHandler", "customCreateIncomingCallChannel called")
273
- maybeCreateChannel(
274
- channelId = INCOMING_CALLS_CUSTOM,
275
- context = application,
276
- configure = {
277
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
278
- name = application.getString(
279
- R.string.stream_video_incoming_call_notification_channel_title,
280
- )
281
- description = application.getString(R.string.stream_video_incoming_call_notification_channel_description)
282
- importance = NotificationManager.IMPORTANCE_HIGH
283
- this.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
284
- this.setShowBadge(true)
285
-
286
- // Set the channel to be silent since we handle sound via RingtonePlayer
287
- setSound(null, null)
288
- enableVibration(true)
289
- enableLights(true)
290
- }
291
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
292
- this.setAllowBubbles(true)
293
- }
294
- },
295
- )
296
- }
297
-
298
- private fun createOngoingCallChannel() {
299
- Log.d("CustomNotificationHandler", "createOngoingCallChannel called")
300
- maybeCreateChannel(
301
- channelId = "ongoing_calls",
302
- context = application,
303
- configure = {
304
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
305
- name = "Ongoing calls"
306
- description = "Notifications for ongoing calls"
307
- importance = NotificationManager.IMPORTANCE_LOW
308
- this.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
309
- this.setShowBadge(false)
310
- setSound(null, null)
311
- enableVibration(false)
312
- enableLights(false)
313
- }
314
- },
315
- )
316
- }
317
-
318
- fun clone(): CustomNotificationHandler {
319
- Log.d("CustomNotificationHandler", "clone called")
320
- return CustomNotificationHandler(this.application, this.endCall, this.incomingCall)
321
- }
322
- }
323
-