@capgo/capacitor-stream-call 0.0.77 → 0.0.79

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.
@@ -75,10 +75,10 @@ dependencies {
75
75
  implementation "androidx.compose.material3:material3:1.3.2"
76
76
 
77
77
  // Stream dependencies
78
- implementation("io.getstream:stream-video-android-ui-compose:1.6.3")
79
- implementation("io.getstream:stream-video-android-core:1.6.3")
80
- implementation("io.getstream:stream-android-push:1.3.1")
81
- implementation("io.getstream:stream-android-push-firebase:1.3.1")
78
+ implementation("io.getstream:stream-video-android-ui-compose:1.9.1")
79
+ implementation("io.getstream:stream-video-android-core:1.9.1")
80
+ implementation("io.getstream:stream-android-push:1.3.2")
81
+ implementation("io.getstream:stream-android-push-firebase:1.3.2")
82
82
 
83
83
  // Firebase dependencies using BOM
84
84
  implementation(platform('com.google.firebase:firebase-bom:33.13.0'))
@@ -18,6 +18,15 @@
18
18
  android:name="ee.forgr.capacitor.streamcall.StreamCallBackgroundService"
19
19
  android:enabled="true"
20
20
  android:exported="false" />
21
+
22
+ <receiver
23
+ android:name=".AcceptCallReceiver"
24
+ android:enabled="true"
25
+ android:exported="true">
26
+ <intent-filter>
27
+ <action android:name="io.getstream.video.android.action.ACCEPT_CALL" />
28
+ </intent-filter>
29
+ </receiver>
21
30
  </application>
22
31
 
23
32
  <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
@@ -0,0 +1,35 @@
1
+ package ee.forgr.capacitor.streamcall
2
+
3
+ import android.content.BroadcastReceiver
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.util.Log
7
+ import io.getstream.video.android.core.notifications.NotificationHandler
8
+ import io.getstream.video.android.model.streamCallId
9
+
10
+ class AcceptCallReceiver : BroadcastReceiver() {
11
+ override fun onReceive(context: Context?, intent: Intent?) {
12
+ Log.d("AcceptCallReceiver", "onReceive called with action: ${intent?.action}")
13
+ if (intent?.action == NotificationHandler.ACTION_ACCEPT_CALL) {
14
+ val cid = intent.streamCallId(NotificationHandler.INTENT_EXTRA_CALL_CID)
15
+ if (cid == null) {
16
+ Log.e("AcceptCallReceiver", "Call CID is null, cannot accept call.")
17
+ return
18
+ }
19
+
20
+ Log.d("AcceptCallReceiver", "Accepting call with CID: $cid")
21
+
22
+ // Create an intent to launch the main activity
23
+ val launchIntent = context?.packageManager?.getLaunchIntentForPackage(context.packageName)
24
+ if (launchIntent != null) {
25
+ launchIntent.action = NotificationHandler.ACTION_ACCEPT_CALL
26
+ launchIntent.putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, cid)
27
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
28
+ context.startActivity(launchIntent)
29
+ Log.d("AcceptCallReceiver", "Started MainActivity to handle ACCEPT_CALL action.")
30
+ } else {
31
+ Log.e("AcceptCallReceiver", "Could not get launch intent for package.")
32
+ }
33
+ }
34
+ }
35
+ }
@@ -14,6 +14,7 @@ import io.getstream.video.android.core.notifications.DefaultNotificationHandler
14
14
  import io.getstream.video.android.core.notifications.NotificationHandler
15
15
  import io.getstream.video.android.model.StreamCallId
16
16
  import io.getstream.video.android.model.streamCallId
17
+ import io.getstream.video.android.core.R
17
18
 
18
19
  // declare "incoming_calls_custom" as a constant
19
20
  const val INCOMING_CALLS_CUSTOM = "incoming_calls_custom"
@@ -67,26 +68,16 @@ class CustomNotificationHandler(
67
68
  )
68
69
 
69
70
  val acceptCallAction = NotificationHandler.ACTION_ACCEPT_CALL
70
- val acceptCallIntent = Intent(acceptCallAction)
71
- // Pass full Parcelable so both new and old handlers succeed
72
- .putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
73
- .setPackage(application.packageName)
74
-
75
- if (targetComponent != null) {
76
- acceptCallIntent.component = targetComponent
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)
77
75
  }
78
- acceptCallIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
79
76
 
80
- Log.d("CustomNotificationHandler", "Constructed Accept Call Intent for PI: action=${acceptCallIntent.action}, cid=${acceptCallIntent.streamCallId(NotificationHandler.INTENT_EXTRA_CALL_CID)}, package=${acceptCallIntent.getPackage()}, component=${acceptCallIntent.component?.flattenToString()}, flags=${acceptCallIntent.flags}")
77
+ Log.d("CustomNotificationHandler", "Constructed Accept Call Intent for PI: action=${acceptCallIntent.action}, cid=${acceptCallIntent.streamCallId(NotificationHandler.INTENT_EXTRA_CALL_CID)}, component=${acceptCallIntent.component}")
81
78
 
82
- // Create PendingIntent for Accept action using getActivity to launch the app
83
79
  val requestCodeAccept = callId.cid.hashCode() + 1 // Unique request code for the PendingIntent with offset to avoid collisions
84
- val acceptCallPendingIntent = PendingIntent.getActivity(
85
- application,
86
- requestCodeAccept,
87
- acceptCallIntent,
88
- PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
89
- )
80
+ val acceptCallPendingIntent = createAcceptCallPendingIntent(callId, requestCodeAccept, acceptCallIntent)
90
81
  Log.d("CustomNotificationHandler", "Created Accept Call PendingIntent with requestCode: $requestCodeAccept")
91
82
 
92
83
  val rejectCallPendingIntent = intentResolver.searchRejectCallPendingIntent(callId) // Keep using resolver for reject for now, or change it too if needed
@@ -126,6 +117,37 @@ class CustomNotificationHandler(
126
117
  null
127
118
  }
128
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
+ }
129
151
 
130
152
  fun customGetIncomingCallNotification(
131
153
  fullScreenPendingIntent: PendingIntent,
@@ -206,6 +228,45 @@ class CustomNotificationHandler(
206
228
  endCall(callId)
207
229
  super.onMissedCall(callId, callDisplayName)
208
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
+ }
209
270
 
210
271
  private fun customCreateIncomingCallChannel() {
211
272
  Log.d("CustomNotificationHandler", "customCreateIncomingCallChannel called")
@@ -233,6 +294,26 @@ class CustomNotificationHandler(
233
294
  },
234
295
  )
235
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
+ }
236
317
 
237
318
  public fun clone(): CustomNotificationHandler {
238
319
  Log.d("CustomNotificationHandler", "clone called")
@@ -410,6 +410,7 @@ public class StreamCallPlugin : Plugin() {
410
410
 
411
411
  CallContent(
412
412
  call = activeCall,
413
+ enableInPictureInPicture = false,
413
414
  onBackPressed = { /* Handle back press if needed */ },
414
415
  controlsContent = { /* Empty to disable native controls */ },
415
416
  appBarContent = { /* Empty to disable app bar with stop call button */ },
@@ -510,6 +511,7 @@ public class StreamCallPlugin : Plugin() {
510
511
 
511
512
  streamVideoClient = null
512
513
  state = State.NOT_INITIALIZED
514
+ eventHandlersRegistered = false
513
515
 
514
516
  val ret = JSObject()
515
517
  ret.put("success", true)
@@ -550,7 +552,10 @@ public class StreamCallPlugin : Plugin() {
550
552
  android.util.Log.v("StreamCallPlugin", "Plugin's streamVideoClient is null, reusing singleton and registering event handlers")
551
553
  streamVideoClient = StreamVideo.instance()
552
554
  // Register event handlers since streamVideoClient was null
553
- registerEventHandlers()
555
+ if (!eventHandlersRegistered) {
556
+ registerEventHandlers()
557
+ eventHandlersRegistered = true
558
+ }
554
559
  } else {
555
560
  android.util.Log.v("StreamCallPlugin", "Plugin already has streamVideoClient, skipping event handler registration")
556
561
  }
@@ -641,8 +646,10 @@ public class StreamCallPlugin : Plugin() {
641
646
  this.state = State.INITIALIZED
642
647
  return
643
648
  }
644
-
645
- registerEventHandlers()
649
+ if (!eventHandlersRegistered) {
650
+ registerEventHandlers()
651
+ eventHandlersRegistered = true
652
+ }
646
653
 
647
654
  android.util.Log.v("StreamCallPlugin", "Initialization finished")
648
655
  initializationTime = System.currentTimeMillis()
@@ -900,7 +907,7 @@ public class StreamCallPlugin : Plugin() {
900
907
  if (connectionState != RealtimeConnection.Disconnected) {
901
908
  val total = activeCall.state.participantCounts.value?.total
902
909
  android.util.Log.d("StreamCallPlugin", "CallSessionParticipantLeftEvent: Participant left, remaining: $total");
903
- if (total != null && total <= 2) {
910
+ if (total != null && total < 2) {
904
911
  android.util.Log.d("StreamCallPlugin", "CallSessionParticipantLeftEvent: All remote participants have left call ${activeCall.cid}. Ending call.")
905
912
  kotlinx.coroutines.GlobalScope.launch(Dispatchers.IO) {
906
913
  endCallRaw(activeCall)
@@ -1766,7 +1773,7 @@ public class StreamCallPlugin : Plugin() {
1766
1773
 
1767
1774
  // Use call.state.totalParticipants to get participant count (as per StreamVideo Android SDK docs)
1768
1775
  val totalParticipants = call.state.totalParticipants.value ?: 0
1769
- val shouldEndCall = isCreator || totalParticipants <= 2
1776
+ val shouldEndCall = isCreator || totalParticipants <= 1
1770
1777
 
1771
1778
  android.util.Log.d("StreamCallPlugin", "Call $callId - Creator: $createdBy, CurrentUser: $currentUserId, IsCreator: $isCreator, TotalParticipants: $totalParticipants, ShouldEnd: $shouldEndCall")
1772
1779
 
@@ -2261,13 +2268,22 @@ public class StreamCallPlugin : Plugin() {
2261
2268
  return sharedPrefs.getString(DYNAMIC_API_KEY_PREF, null)
2262
2269
  }
2263
2270
 
2271
+ private fun getDynamicApiKey(context: Context): String? {
2272
+ val sharedPrefs = getApiKeyPreferences(context)
2273
+ return sharedPrefs.getString(DYNAMIC_API_KEY_PREF, null)
2274
+ }
2275
+
2264
2276
  private fun getApiKeyPreferences(): SharedPreferences {
2265
2277
  return context.getSharedPreferences(API_KEY_PREFS_NAME, Context.MODE_PRIVATE)
2266
2278
  }
2267
2279
 
2280
+ private fun getApiKeyPreferences(context: Context): SharedPreferences {
2281
+ return context.getSharedPreferences(API_KEY_PREFS_NAME, Context.MODE_PRIVATE)
2282
+ }
2283
+
2268
2284
  private fun getEffectiveApiKey(context: Context): String {
2269
2285
  // A) Check if the key exists in the custom preference
2270
- val dynamicApiKey = getDynamicApiKey()
2286
+ val dynamicApiKey = getDynamicApiKey(context)
2271
2287
  return if (!dynamicApiKey.isNullOrEmpty() && dynamicApiKey.trim().isNotEmpty()) {
2272
2288
  android.util.Log.d("StreamCallPlugin", "Using dynamic API key")
2273
2289
  dynamicApiKey
@@ -2366,14 +2382,14 @@ public class StreamCallPlugin : Plugin() {
2366
2382
 
2367
2383
  private val acceptCallReceiver = object : BroadcastReceiver() {
2368
2384
  override fun onReceive(context: Context?, intent: Intent?) {
2369
- android.util.Log.d("StreamCallPlugin", "BroadcastReceiver: Received broadcast with action: ${intent?.action}")
2370
2385
  if (intent?.action == "io.getstream.video.android.action.ACCEPT_CALL") {
2386
+ android.util.Log.d("StreamCallPlugin", "BroadcastReceiver: Received broadcast with action: ${intent.action}")
2371
2387
  val cid = intent.streamCallId(NotificationHandler.INTENT_EXTRA_CALL_CID)
2372
- android.util.Log.d("StreamCallPlugin", "BroadcastReceiver: ACCEPT_CALL broadcast received with cid: $cid")
2373
2388
  if (cid != null) {
2374
- android.util.Log.d("StreamCallPlugin", "BroadcastReceiver: Accepting call with cid: $cid")
2389
+ android.util.Log.d("StreamCallPlugin", "BroadcastReceiver: ACCEPT_CALL broadcast received with cid: $cid")
2375
2390
  val call = streamVideoClient?.call(id = cid.id, type = cid.type)
2376
2391
  if (call != null) {
2392
+ android.util.Log.d("StreamCallPlugin", "BroadcastReceiver: Accepting call with cid: $cid")
2377
2393
  kotlinx.coroutines.GlobalScope.launch {
2378
2394
  internalAcceptCall(call, requestPermissionsAfter = !checkPermissions())
2379
2395
  }
@@ -2412,6 +2428,7 @@ public class StreamCallPlugin : Plugin() {
2412
2428
  }
2413
2429
  }
2414
2430
  private var holder: StreamCallPlugin? = null
2431
+ private var eventHandlersRegistered = false
2415
2432
 
2416
2433
  // Constants for SharedPreferences
2417
2434
  private const val API_KEY_PREFS_NAME = "stream_video_api_key_prefs"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-stream-call",
3
- "version": "0.0.77",
3
+ "version": "0.0.79",
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",