@stream-io/react-native-callingx 0.1.0-beta.2 → 0.1.0-beta.3

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 (44) hide show
  1. package/README.md +12 -8
  2. package/android/bin/src/main/AndroidManifest.xml +29 -0
  3. package/android/bin/src/main/java/io/getstream/rn/callingx/notifications/NotificationChannelsManager.kt +104 -0
  4. package/android/src/main/AndroidManifest.xml +0 -1
  5. package/android/src/main/java/io/getstream/rn/callingx/CallingxModule.kt +3 -3
  6. package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationChannelsManager.kt +9 -18
  7. package/dist/module/CallingxModule.js +2 -14
  8. package/dist/module/CallingxModule.js.map +1 -1
  9. package/dist/module/utils/constants.js +36 -9
  10. package/dist/module/utils/constants.js.map +1 -1
  11. package/dist/typescript/src/CallingxModule.d.ts +1 -5
  12. package/dist/typescript/src/CallingxModule.d.ts.map +1 -1
  13. package/dist/typescript/src/types.d.ts +41 -10
  14. package/dist/typescript/src/types.d.ts.map +1 -1
  15. package/dist/typescript/src/utils/constants.d.ts.map +1 -1
  16. package/ios/CallingxImpl.swift +19 -18
  17. package/package.json +2 -2
  18. package/src/CallingxModule.ts +2 -30
  19. package/src/types.ts +35 -15
  20. package/src/utils/constants.ts +25 -16
  21. package/android/bin/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +0 -33
  22. package/android/bin/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +0 -18
  23. package/android/bin/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +0 -6
  24. package/android/bin/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +0 -4
  25. package/android/bin/build/intermediates/incremental/debug/packageDebugResources/merger.xml +0 -2
  26. package/android/bin/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +0 -60
  27. package/android/bin/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +0 -33
  28. package/android/bin/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +0 -1
  29. package/android/bin/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +0 -1
  30. package/android/bin/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_phone_paused_24.xml +0 -11
  31. package/android/bin/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_round_call_24.xml +0 -11
  32. package/android/bin/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_user.xml +0 -27
  33. package/android/bin/build/outputs/logs/manifest-merger-debug-report.txt +0 -70
  34. package/android/bin/src/main/java/io/getstream/rn/callingx/CallService.kt +0 -402
  35. package/android/bin/src/main/java/io/getstream/rn/callingx/CallingxModule.kt +0 -644
  36. package/android/bin/src/main/java/io/getstream/rn/callingx/notifications/CallNotificationManager.kt +0 -245
  37. package/android/bin/src/main/java/io/getstream/rn/callingx/repo/CallRepository.kt +0 -163
  38. package/android/bin/src/main/java/io/getstream/rn/callingx/repo/LegacyCallRepository.kt +0 -139
  39. package/android/bin/src/main/java/io/getstream/rn/callingx/repo/TelecomCallRepository.kt +0 -432
  40. package/dist/module/utils/permissions.js +0 -86
  41. package/dist/module/utils/permissions.js.map +0 -1
  42. package/dist/typescript/src/utils/permissions.d.ts +0 -8
  43. package/dist/typescript/src/utils/permissions.d.ts.map +0 -1
  44. package/src/utils/permissions.ts +0 -111
@@ -1,644 +0,0 @@
1
- package io.getstream.rn.callingx
2
-
3
- import android.content.BroadcastReceiver
4
- import android.content.ComponentName
5
- import android.content.Context
6
- import android.content.Intent
7
- import android.content.IntentFilter
8
- import android.content.ServiceConnection
9
- import android.os.Build
10
- import android.os.Bundle
11
- import android.os.IBinder
12
- import android.telecom.DisconnectCause
13
- import android.util.Log
14
- import androidx.core.content.ContextCompat
15
- import androidx.core.net.toUri
16
- import com.facebook.react.bridge.Arguments
17
- import com.facebook.react.bridge.LifecycleEventListener
18
- import com.facebook.react.bridge.Promise
19
- import com.facebook.react.bridge.ReactApplicationContext
20
- import com.facebook.react.bridge.ReadableMap
21
- import com.facebook.react.bridge.WritableArray
22
- import com.facebook.react.bridge.WritableMap
23
- import com.facebook.react.bridge.WritableNativeArray
24
- import com.facebook.react.module.annotations.ReactModule
25
- import com.facebook.react.modules.core.DeviceEventManagerModule
26
- import io.getstream.rn.callingx.model.CallAction
27
- import io.getstream.rn.callingx.notifications.NotificationChannelsManager
28
- import io.getstream.rn.callingx.notifications.NotificationsConfig
29
-
30
- @ReactModule(name = CallingxModule.NAME)
31
- class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec(reactContext) {
32
-
33
- companion object {
34
- const val TAG = "[Callingx] CallingxModule"
35
- const val NAME = "Callingx"
36
-
37
- const val EXTRA_CALL_ID = "call_id"
38
- const val EXTRA_MUTED = "is_muted"
39
- const val EXTRA_ON_HOLD = "hold"
40
- const val EXTRA_DISCONNECT_CAUSE = "disconnect_cause"
41
- const val EXTRA_AUDIO_ENDPOINT = "audio_endpoint"
42
- const val EXTRA_SOURCE = "source"
43
-
44
- const val CALL_REGISTERED_ACTION = "call_registered"
45
- const val CALL_REGISTERED_INCOMING_ACTION = "call_registered_incoming"
46
- const val CALL_ANSWERED_ACTION = "call_answered"
47
- // const val CALL_DISCONNECTED_ACTION = "call_disconnected"
48
- const val CALL_INACTIVE_ACTION = "call_inactive"
49
- const val CALL_ACTIVE_ACTION = "call_active"
50
- const val CALL_MUTED_ACTION = "call_muted"
51
- const val CALL_ENDPOINT_CHANGED_ACTION = "call_endpoint_changed"
52
- const val CALL_END_ACTION = "call_end"
53
- // Background task name
54
- const val HEADLESS_TASK_NAME = "HandleCallBackgroundState"
55
- const val SERVICE_READY_ACTION = "service_ready"
56
- }
57
-
58
- private enum class BindingState {
59
- UNBOUND,
60
- BINDING,
61
- BOUND
62
- }
63
-
64
- private var callService: CallService? = null
65
- private var bindingState = BindingState.UNBOUND
66
-
67
- private var delayedEvents = WritableNativeArray()
68
- private var isModuleInitialized = false
69
- private var canSendEvents = false
70
- private var isHeadlessTaskRegistered = false
71
-
72
- private val notificationChannelsManager = NotificationChannelsManager(reactApplicationContext)
73
- private val callEventBroadcastReceiver = CallEventBroadcastReceiver()
74
- private val appStateListener =
75
- object : LifecycleEventListener {
76
- override fun onHostResume() {}
77
-
78
- override fun onHostPause() {}
79
-
80
- override fun onHostDestroy() {
81
- // App destroyed - force unbind
82
- debugLog(TAG, "[module] onHostDestroy: App destroyed")
83
- unbindServiceSafely()
84
- }
85
- }
86
-
87
- init {
88
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
89
- reactContext.registerReceiver(
90
- callEventBroadcastReceiver,
91
- getReceiverFilter(),
92
- Context.RECEIVER_NOT_EXPORTED
93
- )
94
- } else {
95
- @Suppress("UnspecifiedRegisterReceiverFlag")
96
- reactContext.registerReceiver(callEventBroadcastReceiver, getReceiverFilter())
97
- }
98
- }
99
-
100
- override fun getName(): String = NAME
101
-
102
- override fun initialize() {
103
- super.initialize()
104
- reactApplicationContext.addLifecycleEventListener(appStateListener)
105
-
106
- tryToBindIfNeeded()
107
-
108
- debugLog(TAG, "[module] initialize: Initializing module")
109
- }
110
-
111
- override fun invalidate() {
112
- super.invalidate()
113
- debugLog(TAG, "[module] invalidate: Invalidating module")
114
-
115
- unbindServiceSafely()
116
-
117
- reactApplicationContext.removeLifecycleEventListener(appStateListener)
118
- reactApplicationContext.unregisterReceiver(callEventBroadcastReceiver)
119
- isModuleInitialized = false
120
- }
121
-
122
- override fun setupiOS(options: ReadableMap) {
123
- // leave empty
124
- }
125
-
126
- override fun setupAndroid(options: ReadableMap) {
127
- debugLog(TAG, "[module] setupAndroid: Setting up Android: $options")
128
- val notificationsConfig =
129
- NotificationsConfig.saveNotificationsConfig(reactApplicationContext, options)
130
- notificationChannelsManager.setNotificationsConfig(notificationsConfig)
131
- notificationChannelsManager.createNotificationChannels()
132
-
133
- isModuleInitialized = true
134
- }
135
-
136
- override fun canPostNotifications(): Boolean {
137
- return notificationChannelsManager.getNotificationStatus().canPost
138
- }
139
-
140
- override fun setShouldRejectCallWhenBusy(shouldReject: Boolean) {
141
- // leave empty
142
- }
143
-
144
- override fun getInitialVoipEvents(): WritableArray {
145
- // leave empty
146
- return Arguments.createArray()
147
- }
148
-
149
- override fun registerVoipToken() {
150
- // leave empty
151
- }
152
-
153
- override fun getInitialEvents(): WritableArray {
154
- // NOTE: writabel native array can be consumed only once, think of getting rid from clear
155
- // event and clear eat immidiate after getting initial events
156
- val events = delayedEvents
157
- debugLog(TAG, "[module] getInitialEvents: Getting initial events: $events")
158
- delayedEvents = WritableNativeArray()
159
- canSendEvents = true
160
- return events
161
- }
162
-
163
- override fun setCurrentCallActive(callId: String, promise: Promise) {
164
- debugLog(TAG, "[module] activateCall: Activating call: $callId")
165
- executeServiceAction(callId, CallAction.Activate, promise)
166
- }
167
-
168
- override fun displayIncomingCall(
169
- callId: String,
170
- phoneNumber: String,
171
- callerName: String,
172
- hasVideo: Boolean,
173
- displayOptions: ReadableMap?,
174
- promise: Promise
175
- ) {
176
- debugLog(
177
- TAG,
178
- "[module] displayIncomingCall: Displaying incoming call: $callId, $phoneNumber, $callerName, $hasVideo"
179
- )
180
- if (!notificationChannelsManager.getNotificationStatus().canPost) {
181
- promise.reject("ERROR", "Notifications are not granted")
182
- return
183
- }
184
-
185
- startCallService(
186
- CallService.ACTION_INCOMING_CALL,
187
- callId,
188
- callerName,
189
- phoneNumber,
190
- hasVideo,
191
- displayOptions
192
- )
193
-
194
- promise.resolve(true)
195
- }
196
-
197
- override fun answerIncomingCall(callId: String, promise: Promise) {
198
- debugLog(TAG, "[module] answerIncomingCall: Answering call: $callId")
199
- // TODO: get the call type from the call attributes
200
- val isAudioCall = true // TODO: get the call type from the call attributes
201
- // registeredCall.callAttributes.callType ==
202
- // CallAttributesCompat.CALL_TYPE_AUDIO_CALL
203
- // currentCall?.processAction(TelecomCallAction.Answer(isAudioCall))
204
- executeServiceAction(callId, CallAction.Answer(isAudioCall), promise)
205
- }
206
-
207
- override fun startCall(
208
- callId: String,
209
- phoneNumber: String,
210
- callerName: String,
211
- hasVideo: Boolean,
212
- displayOptions: ReadableMap?,
213
- promise: Promise
214
- ) {
215
- debugLog(
216
- TAG,
217
- "[module] startCall: Starting outgoing call: $callId, $phoneNumber, $callerName, $hasVideo, $displayOptions"
218
- )
219
- if (!notificationChannelsManager.getNotificationStatus().canPost) {
220
- promise.reject("ERROR", "Notifications are not granted")
221
- return
222
- }
223
-
224
- startCallService(
225
- CallService.ACTION_OUTGOING_CALL,
226
- callId,
227
- callerName,
228
- phoneNumber,
229
- hasVideo,
230
- displayOptions
231
- )
232
-
233
- promise.resolve(true)
234
- }
235
-
236
- override fun updateDisplay(
237
- callId: String,
238
- phoneNumber: String,
239
- callerName: String,
240
- displayOptions: ReadableMap?,
241
- promise: Promise
242
- ) {
243
- debugLog(TAG, "[module] updateDisplay: Updating display: $callId, $phoneNumber, $callerName")
244
- if (!notificationChannelsManager.getNotificationStatus().canPost) {
245
- promise.reject("ERROR", "Notifications are not granted")
246
- return
247
- }
248
-
249
- // for now only display options will be updated, rest of the parameters will be ignored
250
- startCallService(
251
- CallService.ACTION_UPDATE_CALL,
252
- callId,
253
- callerName,
254
- phoneNumber,
255
- true,
256
- displayOptions,
257
- )
258
- promise.resolve(true)
259
- }
260
-
261
- override fun endCallWithReason(callId: String, reason: Double, promise: Promise) {
262
- debugLog(TAG, "[module] endCallWithReason: Ending call: $callId, $reason")
263
- val action = CallAction.Disconnect(DisconnectCause(reason.toInt()))
264
- executeServiceAction(callId, action, promise)
265
- }
266
-
267
- override fun endCall(callId: String, promise: Promise) {
268
- debugLog(TAG, "[module] endCall: Ending call: $callId")
269
- val action = CallAction.Disconnect(DisconnectCause(DisconnectCause.LOCAL))
270
- executeServiceAction(callId, action, promise)
271
- }
272
-
273
- override fun isCallRegistered(callId: String): Boolean {
274
- val isCallRegistered = callService?.isCallRegistered(callId) ?: false
275
- debugLog(TAG, "[module] isCallRegistered: Is call registered: $isCallRegistered")
276
- return isCallRegistered
277
- }
278
-
279
- override fun hasRegisteredCall(): Boolean {
280
- val hasRegisteredCall = callService?.hasRegisteredCall() ?: false
281
- debugLog(TAG, "[module] hasRegisteredCall: Has registered call: $hasRegisteredCall")
282
- return hasRegisteredCall
283
- }
284
-
285
- override fun setMutedCall(callId: String, isMuted: Boolean, promise: Promise) {
286
- debugLog(TAG, "[module] setMutedCall: Setting muted call: $callId, $isMuted")
287
- val action = CallAction.ToggleMute(isMuted)
288
- executeServiceAction(callId, action, promise)
289
- }
290
-
291
- override fun setOnHoldCall(callId: String, isOnHold: Boolean, promise: Promise) {
292
- debugLog(TAG, "[module] setOnHoldCall: Setting on hold call: $callId, $isOnHold")
293
- val action = if (isOnHold) CallAction.Hold else CallAction.Activate
294
- executeServiceAction(callId, action, promise)
295
- }
296
-
297
- override fun startBackgroundTask(taskName: String, timeout: Double, promise: Promise) {
298
- Intent(reactApplicationContext, CallService::class.java)
299
- .apply {
300
- this.action = CallService.ACTION_START_BACKGROUND_TASK
301
- putExtra(CallService.EXTRA_TASK_NAME, taskName)
302
- putExtra(CallService.EXTRA_TASK_DATA, Bundle())
303
- putExtra(CallService.EXTRA_TASK_TIMEOUT, timeout.toLong())
304
- }
305
- .also { ContextCompat.startForegroundService(reactApplicationContext, it) }
306
-
307
- promise.resolve(true)
308
- }
309
-
310
- override fun stopBackgroundTask(taskName: String, promise: Promise) {
311
- Intent(reactApplicationContext, CallService::class.java)
312
- .apply {
313
- this.action = CallService.ACTION_STOP_BACKGROUND_TASK
314
- putExtra(CallService.EXTRA_TASK_NAME, taskName)
315
- }
316
- .also { ContextCompat.startForegroundService(reactApplicationContext, it) }
317
-
318
- isHeadlessTaskRegistered = false
319
- promise.resolve(true)
320
- }
321
-
322
- override fun registerBackgroundTaskAvailable() {
323
- debugLog(TAG, "[module] registerBackgroundTaskAvailable: Headless task registered")
324
- isHeadlessTaskRegistered = true
325
- }
326
-
327
- override fun isServiceStarted(promise: Promise) {
328
- val isStarted =
329
- bindingState == BindingState.BOUND ||
330
- bindingState == BindingState.BINDING ||
331
- callService?.hasRegisteredCall() == true
332
- debugLog(TAG, "[module] isServiceStarted: Service started: $isStarted")
333
- promise.resolve(isStarted)
334
- }
335
-
336
- override fun log(message: String, level: String) {
337
- when (level) {
338
- "debug" -> debugLog(TAG, "[module] log: $message")
339
- "info" -> Log.i(TAG, "[module] log: $message")
340
- "warn" -> Log.w(TAG, "[module] log: $message")
341
- "error" -> Log.e(TAG, "[module] log: $message")
342
- }
343
- }
344
-
345
- private fun startCallService(
346
- action: String,
347
- callId: String,
348
- callerName: String,
349
- phoneNumber: String,
350
- hasVideo: Boolean,
351
- displayOptions: ReadableMap?
352
- ) {
353
- Intent(reactApplicationContext, CallService::class.java)
354
- .apply {
355
- this.action = action
356
- putExtra(CallService.EXTRA_CALL_ID, callId)
357
- putExtra(CallService.EXTRA_NAME, callerName)
358
- putExtra(CallService.EXTRA_URI, phoneNumber.toUri())
359
- putExtra(CallService.EXTRA_IS_VIDEO, hasVideo)
360
- putExtra(CallService.EXTRA_DISPLAY_OPTIONS, Arguments.toBundle(displayOptions))
361
- }
362
- .also { ContextCompat.startForegroundService(reactApplicationContext, it) }
363
- }
364
-
365
- private fun startBackgroundTaskAutomatically(taskName: String, timeout: Long) {
366
- if (!isHeadlessTaskRegistered) {
367
- debugLog(
368
- TAG,
369
- "[module] startBackgroundTaskAutomatically: Headless task is not registered"
370
- )
371
- return
372
- }
373
-
374
- Intent(reactApplicationContext, CallService::class.java)
375
- .apply {
376
- this.action = CallService.ACTION_START_BACKGROUND_TASK
377
- putExtra(CallService.EXTRA_TASK_NAME, taskName)
378
- putExtra(CallService.EXTRA_TASK_DATA, Bundle())
379
- putExtra(CallService.EXTRA_TASK_TIMEOUT, timeout.toLong())
380
- }
381
- .also { ContextCompat.startForegroundService(reactApplicationContext, it) }
382
- }
383
-
384
- private fun executeServiceAction(callId: String, action: CallAction, promise: Promise) {
385
- debugLog(TAG, "[module] executeServiceAction: Executing service action: $action")
386
- when (bindingState) {
387
- BindingState.BOUND -> {
388
- if (callService != null) {
389
- callService?.processAction(callId, action)
390
- promise.resolve(true)
391
- } else {
392
- promise.reject("ERROR", "Service reference lost")
393
- }
394
- }
395
- BindingState.BINDING -> {
396
- debugLog(TAG, "executeServiceAction: Service binding, queueing action")
397
- promise.reject(
398
- "SERVICE_BINDING",
399
- "Service is connecting, please try again in a moment"
400
- )
401
- }
402
- BindingState.UNBOUND -> {
403
- promise.reject(
404
- "SERVICE_NOT_CONNECTED",
405
- "Service not connected. Call may not be active."
406
- )
407
- }
408
- }
409
- }
410
-
411
- private fun sendJSEvent(eventName: String, params: WritableMap? = null) {
412
- if (isModuleInitialized && reactApplicationContext.hasActiveReactInstance() && canSendEvents
413
- ) {
414
- val paramsMap =
415
- Arguments.createMap().apply {
416
- params?.let {
417
- it.toHashMap().forEach { key, value ->
418
- if (value is Boolean) {
419
- putBoolean(key, value)
420
- } else {
421
- putString(key, value.toString())
422
- }
423
- }
424
- }
425
- }
426
- reactApplicationContext
427
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
428
- .emit(eventName, params)
429
-
430
- val value =
431
- Arguments.createMap().apply {
432
- putString("eventName", eventName)
433
- putMap("params", paramsMap)
434
- }
435
- emitOnNewEvent(value)
436
- } else {
437
- debugLog(TAG, "[module] sendJSEvent: Queueing event: $eventName, $params")
438
- Arguments.createMap()
439
- .apply {
440
- putString("eventName", eventName)
441
- putMap("params", params)
442
- }
443
- .also { delayedEvents.pushMap(it) }
444
- }
445
- }
446
-
447
- private fun getReceiverFilter(): IntentFilter =
448
- IntentFilter().apply {
449
- addAction(CALL_REGISTERED_ACTION)
450
- addAction(CALL_REGISTERED_INCOMING_ACTION)
451
- addAction(CALL_ANSWERED_ACTION)
452
- addAction(CALL_ACTIVE_ACTION)
453
- addAction(CALL_INACTIVE_ACTION)
454
- addAction(CALL_MUTED_ACTION)
455
- addAction(CALL_ENDPOINT_CHANGED_ACTION)
456
- addAction(CALL_END_ACTION)
457
- addAction(SERVICE_READY_ACTION)
458
- }
459
-
460
- private fun bindToServiceIfNeeded() {
461
- when (bindingState) {
462
- BindingState.BOUND -> {
463
- debugLog(TAG, "[module] bindToServiceIfNeeded: Already bound")
464
- return
465
- }
466
- BindingState.BINDING -> {
467
- debugLog(TAG, "[module] bindToServiceIfNeeded: Already binding")
468
- return
469
- }
470
- BindingState.UNBOUND -> {
471
- debugLog(TAG, "[module] bindToServiceIfNeeded: Attempting to bind")
472
- val intent = Intent(reactApplicationContext, CallService::class.java)
473
- try {
474
- val success =
475
- reactApplicationContext.bindService(
476
- intent,
477
- serviceConnection,
478
- Context.BIND_AUTO_CREATE or Context.BIND_IMPORTANT
479
- )
480
- if (success) {
481
- bindingState = BindingState.BINDING
482
- debugLog(TAG, "[module] bindToServiceIfNeeded: Bind request successful")
483
- } else {
484
- Log.e(TAG, "[module] bindToServiceIfNeeded: Bind request failed")
485
- bindingState = BindingState.UNBOUND
486
- }
487
- } catch (e: Exception) {
488
- Log.e(TAG, "[module] bindToServiceIfNeeded: Exception during bind", e)
489
- bindingState = BindingState.UNBOUND
490
- }
491
- }
492
- }
493
- }
494
-
495
- private fun unbindServiceSafely() {
496
- debugLog(TAG, "[module] unbindServiceSafely: Unbinding service")
497
- if (bindingState == BindingState.BOUND || bindingState == BindingState.BINDING) {
498
- try {
499
- reactApplicationContext.unbindService(serviceConnection)
500
- debugLog(TAG, "[module] unbindServiceSafely: Successfully unbound")
501
- } catch (e: IllegalArgumentException) {
502
- Log.w(
503
- TAG,
504
- "[module] unbindServiceSafely: Service not registered or already unbound"
505
- )
506
- } catch (e: Exception) {
507
- Log.e(TAG, "[module] unbindServiceSafely: Error unbinding service", e)
508
- } finally {
509
- bindingState = BindingState.UNBOUND
510
- callService = null
511
- }
512
- }
513
- }
514
-
515
- private fun tryToBindIfNeeded() {
516
- val intent = Intent(reactApplicationContext, CallService::class.java)
517
- try {
518
- val success =
519
- reactApplicationContext.bindService(
520
- intent,
521
- serviceConnection,
522
- 0 // No flags - only bind if service exists
523
- )
524
- if (success) {
525
- bindingState = BindingState.BINDING
526
- debugLog(TAG, "[module] checkForExistingService: Service exists, binding")
527
- } else {
528
- debugLog(TAG, "[module] checkForExistingService: No existing service")
529
- }
530
- } catch (e: Exception) {
531
- Log.e(TAG, "[module] checkForExistingService: Error checking for service", e)
532
- }
533
- }
534
-
535
- private inner class CallEventBroadcastReceiver : BroadcastReceiver() {
536
- override fun onReceive(context: Context, intent: Intent) {
537
- val action = intent.action
538
- val callId = intent.getStringExtra(EXTRA_CALL_ID)
539
-
540
- if (action == null) {
541
- return
542
- }
543
-
544
- debugLog(
545
- TAG,
546
- "[module] onReceive: Received intent: $action callId: $callId callService: ${callService != null}"
547
- )
548
-
549
- if (action == SERVICE_READY_ACTION) {
550
- debugLog(TAG, "[module] onReceive: Service is ready, initiating binding, isHeadlessTaskRegistered: $isHeadlessTaskRegistered")
551
- bindToServiceIfNeeded()
552
- startBackgroundTaskAutomatically(HEADLESS_TASK_NAME, 0L)
553
- return
554
- }
555
-
556
- val params = Arguments.createMap()
557
- if (callId != null) {
558
- params.putString("callId", callId)
559
- }
560
-
561
- when (action) {
562
- CALL_REGISTERED_ACTION -> {
563
- sendJSEvent("didReceiveStartCallAction", params)
564
- }
565
- CALL_REGISTERED_INCOMING_ACTION -> {
566
- sendJSEvent("didDisplayIncomingCall", params)
567
- }
568
- CALL_ANSWERED_ACTION -> {
569
- if (intent.hasExtra(EXTRA_SOURCE)) {
570
- params.putString("source", intent.getStringExtra(EXTRA_SOURCE))
571
- }
572
- sendJSEvent("answerCall", params)
573
- }
574
- CALL_END_ACTION -> {
575
- val source = intent.getStringExtra(EXTRA_SOURCE)
576
- if (source != null) {
577
- params.putString("source", source)
578
- }
579
- if (source == "app") {
580
- // means the call was disconnected, we're ready to unbind the service
581
- unbindServiceSafely()
582
- }
583
- params.putString("cause", intent.getStringExtra(EXTRA_DISCONNECT_CAUSE))
584
- sendJSEvent("endCall", params)
585
- }
586
- CALL_INACTIVE_ACTION -> {
587
- params.putBoolean("hold", true)
588
- sendJSEvent("didToggleHoldCallAction", params)
589
- }
590
- CALL_ACTIVE_ACTION -> {
591
- params.putBoolean("hold", false)
592
- sendJSEvent("didToggleHoldCallAction", params)
593
- }
594
- CALL_MUTED_ACTION -> {
595
- if (intent.hasExtra(EXTRA_MUTED)) {
596
- params.putBoolean("muted", intent.getBooleanExtra(EXTRA_MUTED, false))
597
- }
598
- sendJSEvent("didPerformSetMutedCallAction", params)
599
- }
600
- CALL_ENDPOINT_CHANGED_ACTION -> {
601
- if (intent.hasExtra(EXTRA_AUDIO_ENDPOINT)) {
602
- params.putString("output", intent.getStringExtra(EXTRA_AUDIO_ENDPOINT))
603
- }
604
- sendJSEvent("didChangeAudioRoute", params)
605
- }
606
- }
607
- }
608
- }
609
-
610
- private val serviceConnection =
611
- object : ServiceConnection {
612
- override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
613
- debugLog(TAG, "[module] onServiceConnected: Service connected")
614
- val binder = service as? CallService.CallServiceBinder
615
- callService = binder?.getService()
616
- bindingState = BindingState.BOUND
617
- }
618
-
619
- override fun onServiceDisconnected(name: ComponentName?) {
620
- debugLog(TAG, "onServiceDisconnected: Service disconnected unexpectedly")
621
- callService = null
622
- bindingState = BindingState.UNBOUND
623
- }
624
-
625
- override fun onBindingDied(name: ComponentName?) {
626
- Log.e(TAG, "[module] onBindingDied: Service binding died")
627
- callService = null
628
- bindingState = BindingState.UNBOUND
629
-
630
- // Must unbind to clean up the dead binding
631
- try {
632
- reactApplicationContext.unbindService(this)
633
- } catch (e: Exception) {
634
- Log.w(TAG, "[module] onBindingDied: Error unbinding dead connection", e)
635
- }
636
- }
637
-
638
- override fun onNullBinding(name: ComponentName?) {
639
- Log.e(TAG, "[module] onNullBinding: Service returned null binding")
640
- bindingState = BindingState.UNBOUND
641
- callService = null
642
- }
643
- }
644
- }