@stream-io/react-native-callingx 0.1.1-beta.0 → 0.1.1

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.
Files changed (27) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +45 -357
  3. package/android/src/main/java/io/getstream/rn/callingx/CallRegistrationStore.kt +34 -65
  4. package/android/src/main/java/io/getstream/rn/callingx/CallService.kt +45 -43
  5. package/android/src/main/java/io/getstream/rn/callingx/CallingxModuleImpl.kt +8 -235
  6. package/android/src/main/java/io/getstream/rn/callingx/notifications/CallNotificationManager.kt +9 -25
  7. package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationIntentFactory.kt +18 -6
  8. package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverActivity.kt +41 -18
  9. package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverService.kt +24 -0
  10. package/dist/module/CallingxModule.js +7 -14
  11. package/dist/module/CallingxModule.js.map +1 -1
  12. package/dist/module/spec/NativeCallingx.js.map +1 -1
  13. package/dist/module/utils/constants.js +0 -1
  14. package/dist/module/utils/constants.js.map +1 -1
  15. package/dist/typescript/src/CallingxModule.d.ts +1 -2
  16. package/dist/typescript/src/CallingxModule.d.ts.map +1 -1
  17. package/dist/typescript/src/spec/NativeCallingx.d.ts +0 -3
  18. package/dist/typescript/src/spec/NativeCallingx.d.ts.map +1 -1
  19. package/dist/typescript/src/types.d.ts +2 -5
  20. package/dist/typescript/src/types.d.ts.map +1 -1
  21. package/dist/typescript/src/utils/constants.d.ts +1 -2
  22. package/dist/typescript/src/utils/constants.d.ts.map +1 -1
  23. package/package.json +2 -2
  24. package/src/CallingxModule.ts +6 -11
  25. package/src/spec/NativeCallingx.ts +0 -3
  26. package/src/types.ts +2 -4
  27. package/src/utils/constants.ts +0 -3
@@ -48,13 +48,15 @@ class CallService : Service(), CallRepository.Listener {
48
48
  companion object {
49
49
  private const val TAG = "[Callingx] CallService"
50
50
 
51
+ internal const val DEFAULT_DISPLAY_NAME = "Unknown Caller"
52
+
51
53
  internal const val EXTRA_CALL_ID = "extra_call_id"
52
54
  internal const val EXTRA_NAME = "extra_name"
53
55
  internal const val EXTRA_URI = "extra_uri"
54
56
  internal const val EXTRA_IS_VIDEO = "extra_is_video"
55
57
  internal const val EXTRA_DISPLAY_TITLE = "displayTitle"
56
- internal const val EXTRA_DISPLAY_SUBTITLE = "displaySubtitle"
57
58
  internal const val EXTRA_DISPLAY_OPTIONS = "display_options"
59
+ internal const val EXTRA_ACTION = "action_name"
58
60
  // Background task extras
59
61
  internal const val EXTRA_TASK_NAME = "task_name"
60
62
  internal const val EXTRA_TASK_DATA = "task_data"
@@ -66,6 +68,7 @@ class CallService : Service(), CallRepository.Listener {
66
68
  internal const val ACTION_START_BACKGROUND_TASK = "start_background_task"
67
69
  internal const val ACTION_STOP_BACKGROUND_TASK = "stop_background_task"
68
70
  internal const val ACTION_STOP_SERVICE = "stop_service"
71
+ internal const val ACTION_PROCESS_ACTION = "execute_action"
69
72
  internal const val ACTION_REGISTRATION_FAILED = "registration_failed"
70
73
 
71
74
  fun startIncomingCallFromPush(context: Context, data: Map<String, String>) {
@@ -103,8 +106,12 @@ class CallService : Service(), CallRepository.Listener {
103
106
  )
104
107
  return
105
108
  }
109
+
110
+ val createdById = data["created_by_id"]
111
+ val createdName = data["created_by_display_name"].orEmpty()
112
+ val displayName = data["call_display_name"].orEmpty()
113
+ val callDisplayName = displayName.ifEmpty { createdName.ifEmpty { DEFAULT_DISPLAY_NAME } }
106
114
 
107
- val callName = data["created_by_display_name"].orEmpty()
108
115
  val isVideo = data["video"] == "true"
109
116
 
110
117
  CallRegistrationStore.trackCallRegistration(callCid, null)
@@ -113,8 +120,8 @@ class CallService : Service(), CallRepository.Listener {
113
120
  Intent(context, CallService::class.java).apply {
114
121
  action = ACTION_INCOMING_CALL
115
122
  putExtra(EXTRA_CALL_ID, callCid)
116
- putExtra(EXTRA_URI, callCid.toUri())
117
- putExtra(EXTRA_NAME, callName)
123
+ putExtra(EXTRA_URI, createdById?.toUri() ?: callDisplayName.toUri())
124
+ putExtra(EXTRA_NAME, callDisplayName)
118
125
  putExtra(EXTRA_IS_VIDEO, isVideo)
119
126
  }
120
127
 
@@ -218,8 +225,6 @@ class CallService : Service(), CallRepository.Listener {
218
225
  @Suppress("UnspecifiedRegisterReceiverFlag")
219
226
  registerReceiver(optimisticNotificationReceiver, filter)
220
227
  }
221
-
222
- sendBroadcastEvent(CallingxModuleImpl.SERVICE_READY_ACTION)
223
228
  }
224
229
 
225
230
  override fun onDestroy() {
@@ -272,6 +277,9 @@ class CallService : Service(), CallRepository.Listener {
272
277
  ACTION_UPDATE_CALL -> {
273
278
  updateCall(intent)
274
279
  }
280
+ ACTION_PROCESS_ACTION -> {
281
+ processAction(intent)
282
+ }
275
283
  ACTION_STOP_SERVICE -> {
276
284
  if (isInForeground) {
277
285
  stopForeground(STOP_FOREGROUND_REMOVE)
@@ -416,7 +424,18 @@ class CallService : Service(), CallRepository.Listener {
416
424
  }
417
425
  }
418
426
 
419
- public fun processAction(callId: String, action: CallAction) {
427
+ fun processAction(intent: Intent) {
428
+ val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: return
429
+ val action = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
430
+ intent.getParcelableExtra(EXTRA_ACTION, CallAction::class.java)
431
+ } else {
432
+ @Suppress("DEPRECATION") intent.getParcelableExtra(EXTRA_ACTION)
433
+ } ?: return
434
+
435
+ processAction(callId, action)
436
+ }
437
+
438
+ fun processAction(callId: String, action: CallAction) {
420
439
  debugLog(
421
440
  TAG,
422
441
  "[service] processAction[$callId]: Processing action: ${action::class.simpleName}"
@@ -428,33 +447,23 @@ class CallService : Service(), CallRepository.Listener {
428
447
  } else {
429
448
  // this solves race condition, when action is requested before the call is
430
449
  // registered in Telecom
431
- if (action is CallAction.Disconnect) {
432
- debugLog(TAG, "[service] storing pending disconnect for $callId")
433
- CallRegistrationStore.setPendingDisconnect(callId, action.cause.code)
434
- } else if (action is CallAction.Answer) {
435
- debugLog(TAG, "[service] storing pending answer for $callId")
436
- CallRegistrationStore.setPendingAnswer(callId, action.isAudioCall)
437
- } else if (action is CallAction.ToggleMute) {
438
- debugLog(TAG, "[service] storing pending mute for $callId")
439
- CallRegistrationStore.setPendingMute(callId, action.isMute)
440
- } else {
441
- Log.w(
442
- TAG,
443
- "[service] processAction[$callId]: Call not registered, ignoring action"
444
- )
445
- }
450
+ debugLog(
451
+ TAG,
452
+ "[service] processAction: Add pending action for ${call?.id} to queue"
453
+ )
454
+ CallRegistrationStore.addPendingAction(callId, action)
446
455
  }
447
456
  }
448
457
  }
449
458
 
450
- public fun startBackgroundTask(intent: Intent) {
459
+ fun startBackgroundTask(intent: Intent) {
451
460
  val taskName = intent.getStringExtra(EXTRA_TASK_NAME)!!
452
461
  val data = intent.getBundleExtra(EXTRA_TASK_DATA)!!
453
462
  val timeout = intent.getLongExtra(EXTRA_TASK_TIMEOUT, 0)
454
463
  headlessJSManager.startHeadlessTask(taskName, data, timeout)
455
464
  }
456
465
 
457
- public fun stopBackgroundTask() {
466
+ fun stopBackgroundTask() {
458
467
  headlessJSManager.stopHeadlessTask()
459
468
  }
460
469
 
@@ -518,30 +527,23 @@ class CallService : Service(), CallRepository.Listener {
518
527
 
519
528
  private fun processPendingActions(call: Call.Registered): Boolean {
520
529
  synchronized(actionProcessingLock) {
521
- val pendingCauseCode = CallRegistrationStore.takePendingDisconnect(call.id)
522
- val pendingAnswer = CallRegistrationStore.takePendingAnswer(call.id)
523
- val pendingMute = CallRegistrationStore.takePendingMute(call.id)
530
+ val pendingActions = CallRegistrationStore.takePendingActions(call.id)
524
531
 
525
- if (pendingCauseCode != null) {
526
- debugLog(
527
- TAG,
528
- "[service] onCallStateChanged: Executing pending disconnect for ${call.id}"
529
- )
530
- call.processAction(CallAction.Disconnect(DisconnectCause(pendingCauseCode)))
532
+ val disconnectAction = pendingActions.find { it is CallAction.Disconnect }
533
+ if (disconnectAction != null) {
534
+ // if queue contains Disconnect, execute it and ignore rest of the queue
535
+ debugLog(TAG, "[service] processPendingActions: Executing pending disconnect for ${call.id}")
536
+ call.processAction(disconnectAction)
531
537
  return true
532
538
  }
533
539
 
534
- if (pendingAnswer != null) {
540
+ // process pending actions in the order they were added
541
+ for (action in pendingActions) {
542
+ call.processAction(action)
535
543
  debugLog(
536
- TAG,
537
- "[service] onCallStateChanged: Executing pending answer for ${call.id}"
538
- )
539
- call.processAction(CallAction.Answer(pendingAnswer))
540
- }
541
-
542
- if (pendingMute != null) {
543
- debugLog(TAG, "[service] onCallStateChanged: Executing pending mute for ${call.id}")
544
- call.processAction(CallAction.ToggleMute(pendingMute))
544
+ TAG,
545
+ "[service] processPendingActions: Executing pending action: $action for ${call.id}"
546
+ )
545
547
  }
546
548
 
547
549
  return false
@@ -1,14 +1,11 @@
1
1
  package io.getstream.rn.callingx
2
2
 
3
3
  import android.content.BroadcastReceiver
4
- import android.content.ComponentName
5
4
  import android.content.Context
6
5
  import android.content.Intent
7
6
  import android.content.IntentFilter
8
- import android.content.ServiceConnection
9
7
  import android.os.Build
10
8
  import android.os.Bundle
11
- import android.os.IBinder
12
9
  import android.telecom.DisconnectCause
13
10
  import android.util.Log
14
11
  import androidx.core.content.ContextCompat
@@ -42,6 +39,7 @@ class CallingxModuleImpl(
42
39
  const val EXTRA_DISCONNECT_CAUSE = "disconnect_cause"
43
40
  const val EXTRA_AUDIO_ENDPOINT = "audio_endpoint"
44
41
  const val EXTRA_SOURCE = "source"
42
+ const val EXTRA_ACTION = "action_name"
45
43
 
46
44
  // Action names must match intent-filter entries in AndroidManifest.xml
47
45
  const val CALL_REGISTERED_ACTION = "io.getstream.CALL_REGISTERED"
@@ -56,61 +54,19 @@ class CallingxModuleImpl(
56
54
  const val CALL_OPTIMISTIC_ACCEPT_ACTION = "io.getstream.ACCEPT_CALL_OPTIMISTIC"
57
55
  // Background task name
58
56
  const val HEADLESS_TASK_NAME = "HandleCallBackgroundState"
59
- const val SERVICE_READY_ACTION = "io.getstream.SERVICE_READY"
60
57
  }
61
58
 
62
- private enum class BindingState {
63
- UNBOUND,
64
- BINDING,
65
- BOUND
66
- }
67
-
68
- private var callService: CallService? = null
69
- private var bindingState = BindingState.UNBOUND
70
-
71
59
  private var delayedEvents = WritableNativeArray()
72
60
  private var isModuleInitialized = false
73
61
  private var canSendEvents = false
74
- private var isHeadlessTaskRegistered = false
75
62
 
76
63
  private val notificationChannelsManager = NotificationChannelsManager(reactApplicationContext)
77
- private val serviceReadyBroadcastReceiver = ServiceReadyBroadcastReceiver()
78
- private val appStateListener =
79
- object : LifecycleEventListener {
80
- override fun onHostResume() {}
81
-
82
- override fun onHostPause() {}
83
-
84
- override fun onHostDestroy() {
85
- // App destroyed - force unbind
86
- debugLog(TAG, "[module] onHostDestroy: App destroyed")
87
- unbindServiceSafely()
88
- }
89
- }
90
64
 
91
65
  init {
92
66
  CallEventBus.subscribe(this)
93
-
94
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
95
- reactApplicationContext.registerReceiver(
96
- serviceReadyBroadcastReceiver,
97
- getServiceReadyReceiverFilter(),
98
- Context.RECEIVER_NOT_EXPORTED
99
- )
100
- } else {
101
- @Suppress("UnspecifiedRegisterReceiverFlag")
102
- reactApplicationContext.registerReceiver(
103
- serviceReadyBroadcastReceiver,
104
- getServiceReadyReceiverFilter()
105
- )
106
- }
107
67
  }
108
68
 
109
69
  fun initialize() {
110
- reactApplicationContext.addLifecycleEventListener(appStateListener)
111
-
112
- tryToBindIfNeeded()
113
-
114
70
  debugLog(TAG, "[module] initialize: Initializing module")
115
71
  }
116
72
 
@@ -118,12 +74,8 @@ class CallingxModuleImpl(
118
74
  debugLog(TAG, "[module] invalidate: Invalidating module")
119
75
 
120
76
  CallRegistrationStore.clearAll()
121
- unbindServiceSafely()
122
-
123
77
  CallEventBus.unsubscribe(this)
124
78
 
125
- reactApplicationContext.removeLifecycleEventListener(appStateListener)
126
- reactApplicationContext.unregisterReceiver(serviceReadyBroadcastReceiver)
127
79
  isModuleInitialized = false
128
80
  }
129
81
 
@@ -372,7 +324,6 @@ class CallingxModuleImpl(
372
324
  }
373
325
  .also { reactApplicationContext.startService(it) }
374
326
 
375
- isHeadlessTaskRegistered = false
376
327
  promise.resolve(true)
377
328
  } catch (e: Exception) {
378
329
  Log.e(TAG, "[module] stopBackgroundTask: Failed to start service: ${e.message}", e)
@@ -382,7 +333,6 @@ class CallingxModuleImpl(
382
333
 
383
334
  fun registerBackgroundTaskAvailable() {
384
335
  debugLog(TAG, "[module] registerBackgroundTaskAvailable: Headless task registered")
385
- isHeadlessTaskRegistered = true
386
336
  }
387
337
 
388
338
 
@@ -423,54 +373,16 @@ class CallingxModuleImpl(
423
373
  .also { ContextCompat.startForegroundService(reactApplicationContext, it) }
424
374
  }
425
375
 
426
- private fun startBackgroundTaskAutomatically(taskName: String, timeout: Long) {
427
- if (!isHeadlessTaskRegistered) {
428
- debugLog(
429
- TAG,
430
- "[module] startBackgroundTaskAutomatically: Headless task is not registered"
431
- )
432
- return
433
- }
434
-
435
- try {
436
- Intent(reactApplicationContext, CallService::class.java)
437
- .apply {
438
- this.action = CallService.ACTION_START_BACKGROUND_TASK
439
- putExtra(CallService.EXTRA_TASK_NAME, taskName)
440
- putExtra(CallService.EXTRA_TASK_DATA, Bundle())
441
- putExtra(CallService.EXTRA_TASK_TIMEOUT, timeout.toLong())
442
- }
443
- .also { reactApplicationContext.startService(it) }
444
- } catch (e: Exception) {
445
- Log.e(TAG, "[module] startBackgroundTaskAutomatically: Failed to start service: ${e.message}", e)
446
- }
447
- }
448
-
449
376
  private fun executeServiceAction(callId: String, action: CallAction, promise: Promise) {
450
377
  debugLog(TAG, "[module] executeServiceAction: Executing service action: $action")
451
- when (bindingState) {
452
- BindingState.BOUND -> {
453
- if (callService != null) {
454
- callService?.processAction(callId, action)
455
- promise.resolve(true)
456
- } else {
457
- promise.reject("ERROR", "Service reference lost")
378
+ Intent(reactApplicationContext, CallService::class.java)
379
+ .apply {
380
+ this.action = CallService.ACTION_PROCESS_ACTION
381
+ putExtra(CallService.EXTRA_CALL_ID, callId)
382
+ putExtra(CallService.EXTRA_ACTION, action)
458
383
  }
459
- }
460
- BindingState.BINDING -> {
461
- debugLog(TAG, "executeServiceAction: Service binding, queueing action")
462
- promise.reject(
463
- "SERVICE_BINDING",
464
- "Service is connecting, please try again in a moment"
465
- )
466
- }
467
- BindingState.UNBOUND -> {
468
- promise.reject(
469
- "SERVICE_NOT_CONNECTED",
470
- "Service not connected. Call may not be active."
471
- )
472
- }
473
- }
384
+ .also { reactApplicationContext.startService(it) }
385
+ .also { promise.resolve(true) }
474
386
  }
475
387
 
476
388
  private fun sendJSEvent(eventName: String, params: WritableMap? = null) {
@@ -509,96 +421,11 @@ class CallingxModuleImpl(
509
421
  }
510
422
  }
511
423
 
512
- private fun getServiceReadyReceiverFilter(): IntentFilter =
513
- IntentFilter().apply {
514
- addAction(SERVICE_READY_ACTION)
515
- }
516
-
517
- private fun bindToServiceIfNeeded() {
518
- when (bindingState) {
519
- BindingState.BOUND -> {
520
- debugLog(TAG, "[module] bindToServiceIfNeeded: Already bound")
521
- return
522
- }
523
- BindingState.BINDING -> {
524
- debugLog(TAG, "[module] bindToServiceIfNeeded: Already binding")
525
- return
526
- }
527
- BindingState.UNBOUND -> {
528
- debugLog(TAG, "[module] bindToServiceIfNeeded: Attempting to bind")
529
- val intent = Intent(reactApplicationContext, CallService::class.java)
530
- try {
531
- val success =
532
- reactApplicationContext.bindService(
533
- intent,
534
- serviceConnection,
535
- Context.BIND_AUTO_CREATE or Context.BIND_IMPORTANT
536
- )
537
- if (success) {
538
- bindingState = BindingState.BINDING
539
- debugLog(TAG, "[module] bindToServiceIfNeeded: Bind request successful")
540
- } else {
541
- Log.e(TAG, "[module] bindToServiceIfNeeded: Bind request failed")
542
- bindingState = BindingState.UNBOUND
543
- }
544
- } catch (e: Exception) {
545
- Log.e(TAG, "[module] bindToServiceIfNeeded: Exception during bind", e)
546
- bindingState = BindingState.UNBOUND
547
- }
548
- }
549
- }
550
- }
551
-
552
- private fun unbindServiceSafely() {
553
- debugLog(TAG, "[module] unbindServiceSafely: Unbinding service")
554
- if (bindingState == BindingState.BOUND || bindingState == BindingState.BINDING) {
555
- try {
556
- reactApplicationContext.unbindService(serviceConnection)
557
- debugLog(TAG, "[module] unbindServiceSafely: Successfully unbound")
558
- } catch (e: IllegalArgumentException) {
559
- Log.w(
560
- TAG,
561
- "[module] unbindServiceSafely: Service not registered or already unbound"
562
- )
563
- } catch (e: Exception) {
564
- Log.e(TAG, "[module] unbindServiceSafely: Error unbinding service", e)
565
- } finally {
566
- bindingState = BindingState.UNBOUND
567
- callService = null
568
- }
569
- }
570
- }
571
-
572
- private fun tryToBindIfNeeded() {
573
- val intent = Intent(reactApplicationContext, CallService::class.java)
574
- try {
575
- val success =
576
- reactApplicationContext.bindService(
577
- intent,
578
- serviceConnection,
579
- 0 // No flags - only bind if service exists
580
- )
581
- if (success) {
582
- bindingState = BindingState.BINDING
583
- debugLog(TAG, "[module] checkForExistingService: Service exists, binding")
584
- } else {
585
- debugLog(TAG, "[module] checkForExistingService: No existing service")
586
- }
587
- } catch (e: Exception) {
588
- Log.e(TAG, "[module] checkForExistingService: Error checking for service", e)
589
- }
590
- }
591
-
592
424
  override fun onCallEvent(event: CallEvent) {
593
425
  val action = event.action
594
426
  val extras = event.extras
595
427
  val callId = extras.getString(EXTRA_CALL_ID)
596
428
 
597
- debugLog(
598
- TAG,
599
- "[module] onCallEvent: Received event: $action callId: $callId callService: ${callService != null}"
600
- )
601
-
602
429
  val params = Arguments.createMap()
603
430
  if (callId != null) {
604
431
  params.putString("callId", callId)
@@ -637,10 +464,6 @@ class CallingxModuleImpl(
637
464
  if (callId != null) {
638
465
  CallRegistrationStore.removeTrackedCall(callId)
639
466
  }
640
- // Only unbind when no more calls are tracked
641
- if (!CallRegistrationStore.hasRegisteredCall()) {
642
- unbindServiceSafely()
643
- }
644
467
  }
645
468
  params.putString("cause", extras.getString(EXTRA_DISCONNECT_CAUSE))
646
469
  sendJSEvent("endCall", params)
@@ -667,54 +490,4 @@ class CallingxModuleImpl(
667
490
  }
668
491
  }
669
492
  }
670
-
671
- private inner class ServiceReadyBroadcastReceiver : BroadcastReceiver() {
672
- override fun onReceive(context: Context, intent: Intent) {
673
- val action = intent.action ?: return
674
-
675
- if (action == SERVICE_READY_ACTION) {
676
- debugLog(
677
- TAG,
678
- "[module] ServiceReadyBroadcastReceiver: Service is ready, initiating binding, isHeadlessTaskRegistered: $isHeadlessTaskRegistered"
679
- )
680
- bindToServiceIfNeeded()
681
- startBackgroundTaskAutomatically(HEADLESS_TASK_NAME, 0L)
682
- }
683
- }
684
- }
685
-
686
- private val serviceConnection =
687
- object : ServiceConnection {
688
- override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
689
- debugLog(TAG, "[module] onServiceConnected: Service connected")
690
- val binder = service as? CallService.CallServiceBinder
691
- callService = binder?.getService()
692
- bindingState = BindingState.BOUND
693
- }
694
-
695
- override fun onServiceDisconnected(name: ComponentName?) {
696
- debugLog(TAG, "onServiceDisconnected: Service disconnected unexpectedly")
697
- callService = null
698
- bindingState = BindingState.UNBOUND
699
- }
700
-
701
- override fun onBindingDied(name: ComponentName?) {
702
- Log.e(TAG, "[module] onBindingDied: Service binding died")
703
- callService = null
704
- bindingState = BindingState.UNBOUND
705
-
706
- // Must unbind to clean up the dead binding
707
- try {
708
- reactApplicationContext.unbindService(this)
709
- } catch (e: Exception) {
710
- Log.w(TAG, "[module] onBindingDied: Error unbinding dead connection", e)
711
- }
712
- }
713
-
714
- override fun onNullBinding(name: ComponentName?) {
715
- Log.e(TAG, "[module] onNullBinding: Service returned null binding")
716
- bindingState = BindingState.UNBOUND
717
- callService = null
718
- }
719
- }
720
493
  }
@@ -86,7 +86,6 @@ class CallNotificationManager(
86
86
  val isIncoming: Boolean,
87
87
  val optimisticState: OptimisticState,
88
88
  val displayTitle: String?,
89
- val displaySubtitle: String?,
90
89
  val displayName: CharSequence,
91
90
  val address: Uri
92
91
  )
@@ -101,7 +100,6 @@ class CallNotificationManager(
101
100
  isIncoming = isIncoming(),
102
101
  optimisticState = notificationsState[callId]?.optimisticState ?: OptimisticState.NONE,
103
102
  displayTitle = displayOptions?.getString(CallService.EXTRA_DISPLAY_TITLE),
104
- displaySubtitle = displayOptions?.getString(CallService.EXTRA_DISPLAY_SUBTITLE),
105
103
  displayName = callAttributes.displayName,
106
104
  address = callAttributes.address
107
105
  )
@@ -192,14 +190,6 @@ class CallNotificationManager(
192
190
  else -> null
193
191
  }
194
192
  if (text != null) builder.setContentText(text)
195
- } else {
196
- // If the call is active, we need to set the notification text
197
- // based on the call display options (defined on js side)
198
- call.displayOptions?.let {
199
- if (it.containsKey(CallService.EXTRA_DISPLAY_SUBTITLE)) {
200
- builder.setContentText(it.getString(CallService.EXTRA_DISPLAY_SUBTITLE))
201
- }
202
- }
203
193
  }
204
194
 
205
195
  return builder.build()
@@ -327,25 +317,19 @@ class CallNotificationManager(
327
317
  if (call.isIncoming() && !call.isActive && optimisticState == OptimisticState.NONE) {
328
318
  return NotificationCompat.CallStyle.forIncomingCall(
329
319
  caller,
330
- NotificationIntentFactory.getPendingBroadcastIntent(
331
- context,
332
- CallingxModuleImpl.CALL_END_ACTION,
333
- call.id
334
- ) {
335
- putExtra(
336
- CallingxModuleImpl.EXTRA_DISCONNECT_CAUSE,
337
- getDisconnectCauseString(DisconnectCause(DisconnectCause.REJECTED))
338
- )
339
- putExtra(
340
- CallingxModuleImpl.EXTRA_SOURCE,
341
- CallRepository.EventSource.SYS.name.lowercase()
342
- )
343
- },
320
+ NotificationIntentFactory.getPendingNotificationIntent(
321
+ context,
322
+ CallingxModuleImpl.CALL_END_ACTION,
323
+ call.id,
324
+ CallRepository.EventSource.SYS.name.lowercase(),
325
+ false
326
+ ),
344
327
  NotificationIntentFactory.getPendingNotificationIntent(
345
328
  context,
346
329
  CallingxModuleImpl.CALL_ANSWERED_ACTION,
347
330
  call.id,
348
- CallRepository.EventSource.SYS.name.lowercase()
331
+ CallRepository.EventSource.SYS.name.lowercase(),
332
+ true
349
333
  )
350
334
  )
351
335
  }
@@ -22,10 +22,11 @@ object NotificationIntentFactory {
22
22
  context: Context,
23
23
  action: String,
24
24
  callId: String,
25
- source: String
25
+ source: String,
26
+ includeLaunchActivity: Boolean
26
27
  ): PendingIntent {
27
28
  return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
28
- getReceiverActivityIntent(context, action, callId, source)
29
+ getReceiverActivityIntent(context, action, callId, source, includeLaunchActivity)
29
30
  } else {
30
31
  getPendingServiceIntent(context, action, callId, source)
31
32
  }
@@ -47,7 +48,7 @@ object NotificationIntentFactory {
47
48
  )
48
49
  }
49
50
 
50
- fun getReceiverActivityIntent(context: Context, action: String, callId: String, source: String): PendingIntent {
51
+ fun getReceiverActivityIntent(context: Context, action: String, callId: String, source: String, includeLaunchActivity: Boolean): PendingIntent {
51
52
  val receiverIntent =
52
53
  Intent(context, NotificationReceiverActivity::class.java).apply {
53
54
  this.action = action
@@ -57,14 +58,25 @@ object NotificationIntentFactory {
57
58
 
58
59
  val launchActivity = context.packageManager.getLaunchIntentForPackage(context.packageName)
59
60
  val launchActivityIntent =
60
- Intent(launchActivity).apply {
61
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
61
+ launchActivity?.let { base ->
62
+ Intent(base).apply {
63
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
64
+ }
65
+ }
66
+
67
+ // intents are started in order and build a synthetic back stack
68
+ // the last intent is the one on top, so the launch activity should come first
69
+ val intents =
70
+ if (includeLaunchActivity && launchActivityIntent != null) {
71
+ arrayOf(launchActivityIntent, receiverIntent)
72
+ } else {
73
+ arrayOf(receiverIntent)
62
74
  }
63
75
 
64
76
  return PendingIntent.getActivities(
65
77
  context,
66
78
  requestCodeFor(callId, REQUEST_CODE_RECEIVER_ACTIVITY),
67
- arrayOf(launchActivityIntent, receiverIntent),
79
+ intents,
68
80
  PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
69
81
  )
70
82
  }