@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.
- package/CHANGELOG.md +15 -0
- package/README.md +45 -357
- package/android/src/main/java/io/getstream/rn/callingx/CallRegistrationStore.kt +34 -65
- package/android/src/main/java/io/getstream/rn/callingx/CallService.kt +45 -43
- package/android/src/main/java/io/getstream/rn/callingx/CallingxModuleImpl.kt +8 -235
- package/android/src/main/java/io/getstream/rn/callingx/notifications/CallNotificationManager.kt +9 -25
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationIntentFactory.kt +18 -6
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverActivity.kt +41 -18
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverService.kt +24 -0
- package/dist/module/CallingxModule.js +7 -14
- package/dist/module/CallingxModule.js.map +1 -1
- package/dist/module/spec/NativeCallingx.js.map +1 -1
- package/dist/module/utils/constants.js +0 -1
- package/dist/module/utils/constants.js.map +1 -1
- package/dist/typescript/src/CallingxModule.d.ts +1 -2
- package/dist/typescript/src/CallingxModule.d.ts.map +1 -1
- package/dist/typescript/src/spec/NativeCallingx.d.ts +0 -3
- package/dist/typescript/src/spec/NativeCallingx.d.ts.map +1 -1
- package/dist/typescript/src/types.d.ts +2 -5
- package/dist/typescript/src/types.d.ts.map +1 -1
- package/dist/typescript/src/utils/constants.d.ts +1 -2
- package/dist/typescript/src/utils/constants.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/CallingxModule.ts +6 -11
- package/src/spec/NativeCallingx.ts +0 -3
- package/src/types.ts +2 -4
- 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,
|
|
117
|
-
putExtra(EXTRA_NAME,
|
|
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
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
522
|
-
val pendingAnswer = CallRegistrationStore.takePendingAnswer(call.id)
|
|
523
|
-
val pendingMute = CallRegistrationStore.takePendingMute(call.id)
|
|
530
|
+
val pendingActions = CallRegistrationStore.takePendingActions(call.id)
|
|
524
531
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
-
|
|
540
|
+
// process pending actions in the order they were added
|
|
541
|
+
for (action in pendingActions) {
|
|
542
|
+
call.processAction(action)
|
|
535
543
|
debugLog(
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
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
|
}
|
package/android/src/main/java/io/getstream/rn/callingx/notifications/CallNotificationManager.kt
CHANGED
|
@@ -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.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
}
|
package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationIntentFactory.kt
CHANGED
|
@@ -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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
79
|
+
intents,
|
|
68
80
|
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
|
69
81
|
)
|
|
70
82
|
}
|