@stream-io/react-native-callingx 0.1.0-beta.7 → 0.1.1-beta.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/android/build.gradle +7 -1
- package/android/src/main/AndroidManifest.xml +31 -1
- package/android/src/main/java/io/getstream/rn/callingx/CallEventBroadcastReceiver.kt +17 -0
- package/android/src/main/java/io/getstream/rn/callingx/CallRegistrationStore.kt +145 -0
- package/android/src/main/java/io/getstream/rn/callingx/CallService.kt +301 -83
- package/android/src/main/java/io/getstream/rn/callingx/CallingxModuleImpl.kt +148 -390
- package/android/src/main/java/io/getstream/rn/callingx/StreamMessagingService.kt +48 -0
- package/android/src/main/java/io/getstream/rn/callingx/model/Call.kt +1 -0
- package/android/src/main/java/io/getstream/rn/callingx/notifications/CallNotificationManager.kt +188 -48
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationIntentFactory.kt +14 -8
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverActivity.kt +12 -1
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverService.kt +7 -0
- package/android/src/main/java/io/getstream/rn/callingx/repo/CallRepository.kt +38 -19
- package/android/src/main/java/io/getstream/rn/callingx/repo/LegacyCallRepository.kt +64 -55
- package/android/src/main/java/io/getstream/rn/callingx/repo/TelecomCallRepository.kt +241 -195
- package/android/src/main/java/io/getstream/rn/callingx/utils/CallEventBus.kt +61 -0
- package/android/src/main/java/io/getstream/rn/callingx/utils/SettingsStore.kt +51 -0
- package/android/src/newarch/java/io/getstream/rn/callingx/CallingxModule.kt +12 -3
- package/android/src/oldarch/java/io/getstream/rn/callingx/CallingxModule.kt +13 -3
- package/dist/module/CallingxModule.js +20 -24
- package/dist/module/CallingxModule.js.map +1 -1
- package/dist/module/spec/NativeCallingx.js.map +1 -1
- package/dist/module/utils/constants.js +24 -14
- package/dist/module/utils/constants.js.map +1 -1
- package/dist/typescript/src/CallingxModule.d.ts +4 -2
- package/dist/typescript/src/CallingxModule.d.ts.map +1 -1
- package/dist/typescript/src/spec/NativeCallingx.d.ts +7 -4
- package/dist/typescript/src/spec/NativeCallingx.d.ts.map +1 -1
- package/dist/typescript/src/types.d.ts +33 -5
- package/dist/typescript/src/types.d.ts.map +1 -1
- package/dist/typescript/src/utils/constants.d.ts +2 -3
- package/dist/typescript/src/utils/constants.d.ts.map +1 -1
- package/ios/AudioSessionManager.swift +2 -2
- package/ios/Callingx.mm +41 -17
- package/ios/CallingxImpl.swift +213 -83
- package/ios/Settings.swift +2 -2
- package/ios/UUIDStorage.swift +10 -10
- package/ios/VoipNotificationsManager.swift +8 -8
- package/package.json +4 -2
- package/src/CallingxModule.ts +20 -21
- package/src/spec/NativeCallingx.ts +10 -6
- package/src/types.ts +36 -4
- package/src/utils/constants.ts +23 -12
- /package/android/src/main/java/io/getstream/rn/callingx/{ResourceUtils.kt → utils/ResourceUtils.kt} +0 -0
- /package/android/src/main/java/io/getstream/rn/callingx/{Utils.kt → utils/Utils.kt} +0 -0
|
@@ -1,16 +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.Handler
|
|
12
|
-
import android.os.IBinder
|
|
13
|
-
import android.os.Looper
|
|
14
9
|
import android.telecom.DisconnectCause
|
|
15
10
|
import android.util.Log
|
|
16
11
|
import androidx.core.content.ContextCompat
|
|
@@ -27,12 +22,12 @@ import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
|
27
22
|
import io.getstream.rn.callingx.model.CallAction
|
|
28
23
|
import io.getstream.rn.callingx.notifications.NotificationChannelsManager
|
|
29
24
|
import io.getstream.rn.callingx.notifications.NotificationsConfig
|
|
30
|
-
import
|
|
25
|
+
import io.getstream.rn.callingx.utils.SettingsStore
|
|
31
26
|
|
|
32
27
|
class CallingxModuleImpl(
|
|
33
28
|
private val reactApplicationContext: ReactApplicationContext,
|
|
34
29
|
private val eventEmitter: CallingxEventEmitterAdapter
|
|
35
|
-
) {
|
|
30
|
+
) : CallEventBus.Listener {
|
|
36
31
|
|
|
37
32
|
companion object {
|
|
38
33
|
const val TAG = "[Callingx] CallingxModule"
|
|
@@ -44,100 +39,54 @@ class CallingxModuleImpl(
|
|
|
44
39
|
const val EXTRA_DISCONNECT_CAUSE = "disconnect_cause"
|
|
45
40
|
const val EXTRA_AUDIO_ENDPOINT = "audio_endpoint"
|
|
46
41
|
const val EXTRA_SOURCE = "source"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const val
|
|
51
|
-
|
|
52
|
-
const val
|
|
53
|
-
const val
|
|
54
|
-
const val
|
|
55
|
-
const val
|
|
56
|
-
const val
|
|
57
|
-
const val
|
|
42
|
+
const val EXTRA_ACTION = "action_name"
|
|
43
|
+
|
|
44
|
+
// Action names must match intent-filter entries in AndroidManifest.xml
|
|
45
|
+
const val CALL_REGISTERED_ACTION = "io.getstream.CALL_REGISTERED"
|
|
46
|
+
const val CALL_REGISTERED_INCOMING_ACTION = "io.getstream.CALL_REGISTERED_INCOMING"
|
|
47
|
+
const val CALL_ANSWERED_ACTION = "io.getstream.CALL_ANSWERED"
|
|
48
|
+
const val CALL_INACTIVE_ACTION = "io.getstream.CALL_INACTIVE"
|
|
49
|
+
const val CALL_ACTIVE_ACTION = "io.getstream.CALL_ACTIVE"
|
|
50
|
+
const val CALL_MUTED_ACTION = "io.getstream.CALL_MUTED"
|
|
51
|
+
const val CALL_ENDPOINT_CHANGED_ACTION = "io.getstream.CALL_ENDPOINT_CHANGED"
|
|
52
|
+
const val CALL_END_ACTION = "io.getstream.CALL_END"
|
|
53
|
+
const val CALL_REGISTRATION_FAILED_ACTION = "io.getstream.CALL_REGISTRATION_FAILED"
|
|
54
|
+
const val CALL_OPTIMISTIC_ACCEPT_ACTION = "io.getstream.ACCEPT_CALL_OPTIMISTIC"
|
|
58
55
|
// Background task name
|
|
59
56
|
const val HEADLESS_TASK_NAME = "HandleCallBackgroundState"
|
|
60
|
-
const val SERVICE_READY_ACTION = "service_ready"
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
private enum class BindingState {
|
|
64
|
-
UNBOUND,
|
|
65
|
-
BINDING,
|
|
66
|
-
BOUND
|
|
67
57
|
}
|
|
68
58
|
|
|
69
|
-
private var callService: CallService? = null
|
|
70
|
-
private var bindingState = BindingState.UNBOUND
|
|
71
|
-
|
|
72
59
|
private var delayedEvents = WritableNativeArray()
|
|
73
60
|
private var isModuleInitialized = false
|
|
74
61
|
private var canSendEvents = false
|
|
75
|
-
private var isHeadlessTaskRegistered = false
|
|
76
|
-
|
|
77
|
-
// Synchronous call tracking set, updated before async service start to mirror iOS semantics.
|
|
78
|
-
// This ensures isCallTracked() returns true immediately after displayIncomingCall/startCall.
|
|
79
|
-
private val trackedCallIds = ConcurrentHashMap.newKeySet<String>()
|
|
80
|
-
|
|
81
|
-
// Per-callId pending promises for displayIncomingCall awaiting CALL_REGISTERED_INCOMING_ACTION
|
|
82
|
-
private val pendingDisplayPromises = mutableMapOf<String, Promise>()
|
|
83
|
-
private val pendingTimeouts = mutableMapOf<String, Runnable>()
|
|
84
|
-
private val mainHandler = Handler(Looper.getMainLooper())
|
|
85
|
-
private val displayTimeoutMs = 10_000L // 10 second safety timeout
|
|
86
62
|
|
|
87
63
|
private val notificationChannelsManager = NotificationChannelsManager(reactApplicationContext)
|
|
88
|
-
private val callEventBroadcastReceiver = CallEventBroadcastReceiver()
|
|
89
|
-
private val appStateListener =
|
|
90
|
-
object : LifecycleEventListener {
|
|
91
|
-
override fun onHostResume() {}
|
|
92
|
-
|
|
93
|
-
override fun onHostPause() {}
|
|
94
|
-
|
|
95
|
-
override fun onHostDestroy() {
|
|
96
|
-
// App destroyed - force unbind
|
|
97
|
-
debugLog(TAG, "[module] onHostDestroy: App destroyed")
|
|
98
|
-
unbindServiceSafely()
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
64
|
|
|
102
65
|
init {
|
|
103
|
-
|
|
104
|
-
reactApplicationContext.registerReceiver(
|
|
105
|
-
callEventBroadcastReceiver,
|
|
106
|
-
getReceiverFilter(),
|
|
107
|
-
Context.RECEIVER_NOT_EXPORTED
|
|
108
|
-
)
|
|
109
|
-
} else {
|
|
110
|
-
@Suppress("UnspecifiedRegisterReceiverFlag")
|
|
111
|
-
reactApplicationContext.registerReceiver(callEventBroadcastReceiver, getReceiverFilter())
|
|
112
|
-
}
|
|
66
|
+
CallEventBus.subscribe(this)
|
|
113
67
|
}
|
|
114
68
|
|
|
115
69
|
fun initialize() {
|
|
116
|
-
reactApplicationContext.addLifecycleEventListener(appStateListener)
|
|
117
|
-
|
|
118
|
-
tryToBindIfNeeded()
|
|
119
|
-
|
|
120
70
|
debugLog(TAG, "[module] initialize: Initializing module")
|
|
121
71
|
}
|
|
122
72
|
|
|
123
73
|
fun invalidate() {
|
|
124
74
|
debugLog(TAG, "[module] invalidate: Invalidating module")
|
|
125
75
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
pendingTimeouts.values.forEach { mainHandler.removeCallbacks(it) }
|
|
129
|
-
pendingTimeouts.clear()
|
|
130
|
-
pendingDisplayPromises.clear()
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
trackedCallIds.clear()
|
|
134
|
-
unbindServiceSafely()
|
|
76
|
+
CallRegistrationStore.clearAll()
|
|
77
|
+
CallEventBus.unsubscribe(this)
|
|
135
78
|
|
|
136
|
-
reactApplicationContext.removeLifecycleEventListener(appStateListener)
|
|
137
|
-
reactApplicationContext.unregisterReceiver(callEventBroadcastReceiver)
|
|
138
79
|
isModuleInitialized = false
|
|
139
80
|
}
|
|
140
81
|
|
|
82
|
+
fun setShouldRejectCallWhenBusy(shouldReject: Boolean) {
|
|
83
|
+
debugLog(
|
|
84
|
+
TAG,
|
|
85
|
+
"[module] setShouldRejectCallWhenBusy: Updating rejectCallWhenBusy to $shouldReject"
|
|
86
|
+
)
|
|
87
|
+
SettingsStore.setShouldRejectCallWhenBusy(reactApplicationContext, shouldReject)
|
|
88
|
+
}
|
|
89
|
+
|
|
141
90
|
fun setupAndroid(options: ReadableMap) {
|
|
142
91
|
debugLog(TAG, "[module] setupAndroid: Setting up Android: $options")
|
|
143
92
|
val notificationsConfig =
|
|
@@ -145,6 +94,18 @@ class CallingxModuleImpl(
|
|
|
145
94
|
notificationChannelsManager.setNotificationsConfig(notificationsConfig)
|
|
146
95
|
notificationChannelsManager.createNotificationChannels()
|
|
147
96
|
|
|
97
|
+
val notificationTexts = options.getMap("notificationTexts")
|
|
98
|
+
if (notificationTexts != null) {
|
|
99
|
+
val acceptingText = notificationTexts.getString("accepting")
|
|
100
|
+
val rejectingText = notificationTexts.getString("rejecting")
|
|
101
|
+
debugLog(TAG, "[module] $acceptingText $rejectingText")
|
|
102
|
+
SettingsStore.setOptimisticTexts(
|
|
103
|
+
reactApplicationContext,
|
|
104
|
+
acceptingText,
|
|
105
|
+
rejectingText,
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
148
109
|
isModuleInitialized = true
|
|
149
110
|
}
|
|
150
111
|
|
|
@@ -152,13 +113,30 @@ class CallingxModuleImpl(
|
|
|
152
113
|
return notificationChannelsManager.getNotificationStatus().canPost
|
|
153
114
|
}
|
|
154
115
|
|
|
116
|
+
fun stopService(promise: Promise) {
|
|
117
|
+
debugLog(TAG, "[module] stopService: Stopping CallService explicitly from JS")
|
|
118
|
+
try {
|
|
119
|
+
Intent(reactApplicationContext, CallService::class.java)
|
|
120
|
+
.apply { action = CallService.ACTION_STOP_SERVICE }
|
|
121
|
+
.also { reactApplicationContext.startService(it) }
|
|
122
|
+
|
|
123
|
+
promise.resolve(true)
|
|
124
|
+
} catch (e: Exception) {
|
|
125
|
+
Log.e(TAG, "[module] stopService: Failed to stop service: ${e.message}", e)
|
|
126
|
+
promise.reject("STOP_SERVICE_ERROR", e.message, e)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
155
130
|
fun getInitialEvents(): WritableArray {
|
|
156
|
-
|
|
157
|
-
|
|
131
|
+
CallEventBus.drainPendingEvents().forEach { onCallEvent(it) }
|
|
132
|
+
|
|
133
|
+
// NOTE: writable native array can be consumed only once, think of getting rid from clear
|
|
134
|
+
// event and clear it immediately after getting initial events
|
|
158
135
|
val events = delayedEvents
|
|
159
136
|
debugLog(TAG, "[module] getInitialEvents: Getting initial events: $events")
|
|
160
137
|
delayedEvents = WritableNativeArray()
|
|
161
138
|
canSendEvents = true
|
|
139
|
+
CallEventBus.markJsReady()
|
|
162
140
|
return events
|
|
163
141
|
}
|
|
164
142
|
|
|
@@ -184,30 +162,7 @@ class CallingxModuleImpl(
|
|
|
184
162
|
return
|
|
185
163
|
}
|
|
186
164
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// Store the promise keyed by callId; it will be resolved when CALL_REGISTERED_INCOMING_ACTION
|
|
190
|
-
// broadcast is received, or rejected on timeout / registration failure.
|
|
191
|
-
synchronized(pendingDisplayPromises) {
|
|
192
|
-
// Cancel any existing timeout for this callId to avoid a stale
|
|
193
|
-
// runnable rejecting the new promise after it overwrites the old one.
|
|
194
|
-
pendingTimeouts.remove(callId)?.let { mainHandler.removeCallbacks(it) }
|
|
195
|
-
|
|
196
|
-
pendingDisplayPromises[callId] = promise
|
|
197
|
-
|
|
198
|
-
// Per-call timeout runnable
|
|
199
|
-
val timeoutRunnable = Runnable {
|
|
200
|
-
synchronized(pendingDisplayPromises) {
|
|
201
|
-
pendingDisplayPromises.remove(callId)?.reject(
|
|
202
|
-
"TIMEOUT",
|
|
203
|
-
"Timed out waiting for call registration: $callId"
|
|
204
|
-
)
|
|
205
|
-
pendingTimeouts.remove(callId)
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
pendingTimeouts[callId] = timeoutRunnable
|
|
209
|
-
mainHandler.postDelayed(timeoutRunnable, displayTimeoutMs)
|
|
210
|
-
}
|
|
165
|
+
CallRegistrationStore.trackCallRegistration(callId, promise)
|
|
211
166
|
|
|
212
167
|
try {
|
|
213
168
|
startCallService(
|
|
@@ -220,12 +175,12 @@ class CallingxModuleImpl(
|
|
|
220
175
|
)
|
|
221
176
|
} catch (e: Exception) {
|
|
222
177
|
Log.e(TAG, "[module] displayIncomingCall: Failed to start foreground service: ${e.message}", e)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
178
|
+
CallRegistrationStore.reportRegistrationFail(
|
|
179
|
+
callId,
|
|
180
|
+
"START_FOREGROUND_SERVICE_ERROR",
|
|
181
|
+
e.message,
|
|
182
|
+
e
|
|
183
|
+
)
|
|
229
184
|
}
|
|
230
185
|
}
|
|
231
186
|
|
|
@@ -256,7 +211,7 @@ class CallingxModuleImpl(
|
|
|
256
211
|
return
|
|
257
212
|
}
|
|
258
213
|
|
|
259
|
-
|
|
214
|
+
CallRegistrationStore.trackCallRegistration(callId, promise)
|
|
260
215
|
|
|
261
216
|
try {
|
|
262
217
|
startCallService(
|
|
@@ -267,11 +222,14 @@ class CallingxModuleImpl(
|
|
|
267
222
|
hasVideo,
|
|
268
223
|
displayOptions
|
|
269
224
|
)
|
|
270
|
-
promise.resolve(true)
|
|
271
225
|
} catch (e: Exception) {
|
|
272
226
|
Log.e(TAG, "[module] startCall: Failed to start foreground service: ${e.message}", e)
|
|
273
|
-
|
|
274
|
-
|
|
227
|
+
CallRegistrationStore.reportRegistrationFail(
|
|
228
|
+
callId,
|
|
229
|
+
"START_FOREGROUND_SERVICE_ERROR",
|
|
230
|
+
e.message,
|
|
231
|
+
e
|
|
232
|
+
)
|
|
275
233
|
}
|
|
276
234
|
}
|
|
277
235
|
|
|
@@ -307,28 +265,24 @@ class CallingxModuleImpl(
|
|
|
307
265
|
|
|
308
266
|
fun endCallWithReason(callId: String, reason: Double, promise: Promise) {
|
|
309
267
|
debugLog(TAG, "[module] endCallWithReason: Ending call: $callId, $reason")
|
|
310
|
-
|
|
268
|
+
CallRegistrationStore.removeTrackedCall(callId)
|
|
311
269
|
val action = CallAction.Disconnect(DisconnectCause(reason.toInt()))
|
|
312
270
|
executeServiceAction(callId, action, promise)
|
|
313
271
|
}
|
|
314
272
|
|
|
315
273
|
fun endCall(callId: String, promise: Promise) {
|
|
316
274
|
debugLog(TAG, "[module] endCall: Ending call: $callId")
|
|
317
|
-
|
|
275
|
+
CallRegistrationStore.removeTrackedCall(callId)
|
|
318
276
|
val action = CallAction.Disconnect(DisconnectCause(DisconnectCause.LOCAL))
|
|
319
277
|
executeServiceAction(callId, action, promise)
|
|
320
278
|
}
|
|
321
279
|
|
|
322
280
|
fun isCallTracked(callId: String): Boolean {
|
|
323
|
-
|
|
324
|
-
debugLog(TAG, "[module] isCallTracked: Is call tracked: $isTracked")
|
|
325
|
-
return isTracked
|
|
281
|
+
return CallRegistrationStore.isCallTracked(callId)
|
|
326
282
|
}
|
|
327
283
|
|
|
328
284
|
fun hasRegisteredCall(): Boolean {
|
|
329
|
-
|
|
330
|
-
debugLog(TAG, "[module] hasRegisteredCall: Has registered call: $hasRegisteredCall")
|
|
331
|
-
return hasRegisteredCall
|
|
285
|
+
return CallRegistrationStore.hasRegisteredCall()
|
|
332
286
|
}
|
|
333
287
|
|
|
334
288
|
fun setMutedCall(callId: String, isMuted: Boolean, promise: Promise) {
|
|
@@ -352,12 +306,12 @@ class CallingxModuleImpl(
|
|
|
352
306
|
putExtra(CallService.EXTRA_TASK_DATA, Bundle())
|
|
353
307
|
putExtra(CallService.EXTRA_TASK_TIMEOUT, timeout.toLong())
|
|
354
308
|
}
|
|
355
|
-
.also {
|
|
309
|
+
.also { reactApplicationContext.startService(it) }
|
|
356
310
|
|
|
357
311
|
promise.resolve(true)
|
|
358
312
|
} catch (e: Exception) {
|
|
359
|
-
Log.e(TAG, "[module] startBackgroundTask: Failed to start
|
|
360
|
-
promise.reject("
|
|
313
|
+
Log.e(TAG, "[module] startBackgroundTask: Failed to start service: ${e.message}", e)
|
|
314
|
+
promise.reject("START_SERVICE_ERROR", e.message, e)
|
|
361
315
|
}
|
|
362
316
|
}
|
|
363
317
|
|
|
@@ -368,28 +322,26 @@ class CallingxModuleImpl(
|
|
|
368
322
|
this.action = CallService.ACTION_STOP_BACKGROUND_TASK
|
|
369
323
|
putExtra(CallService.EXTRA_TASK_NAME, taskName)
|
|
370
324
|
}
|
|
371
|
-
.also {
|
|
325
|
+
.also { reactApplicationContext.startService(it) }
|
|
372
326
|
|
|
373
|
-
isHeadlessTaskRegistered = false
|
|
374
327
|
promise.resolve(true)
|
|
375
328
|
} catch (e: Exception) {
|
|
376
|
-
Log.e(TAG, "[module] stopBackgroundTask: Failed to start
|
|
377
|
-
promise.reject("
|
|
329
|
+
Log.e(TAG, "[module] stopBackgroundTask: Failed to start service: ${e.message}", e)
|
|
330
|
+
promise.reject("START_SERVICE_ERROR", e.message, e)
|
|
378
331
|
}
|
|
379
332
|
}
|
|
380
333
|
|
|
381
334
|
fun registerBackgroundTaskAvailable() {
|
|
382
335
|
debugLog(TAG, "[module] registerBackgroundTaskAvailable: Headless task registered")
|
|
383
|
-
isHeadlessTaskRegistered = true
|
|
384
336
|
}
|
|
385
337
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
338
|
+
|
|
339
|
+
fun fulfillAnswerCallAction(callId: String, didFail: Boolean) {
|
|
340
|
+
// no-op: Android Telecom doesn't require explicit action fulfillment
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
fun fulfillEndCallAction(callId: String, didFail: Boolean) {
|
|
344
|
+
// no-op: Android Telecom doesn't require explicit action fulfillment
|
|
393
345
|
}
|
|
394
346
|
|
|
395
347
|
fun log(message: String, level: String) {
|
|
@@ -421,54 +373,16 @@ class CallingxModuleImpl(
|
|
|
421
373
|
.also { ContextCompat.startForegroundService(reactApplicationContext, it) }
|
|
422
374
|
}
|
|
423
375
|
|
|
424
|
-
private fun startBackgroundTaskAutomatically(taskName: String, timeout: Long) {
|
|
425
|
-
if (!isHeadlessTaskRegistered) {
|
|
426
|
-
debugLog(
|
|
427
|
-
TAG,
|
|
428
|
-
"[module] startBackgroundTaskAutomatically: Headless task is not registered"
|
|
429
|
-
)
|
|
430
|
-
return
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
try {
|
|
434
|
-
Intent(reactApplicationContext, CallService::class.java)
|
|
435
|
-
.apply {
|
|
436
|
-
this.action = CallService.ACTION_START_BACKGROUND_TASK
|
|
437
|
-
putExtra(CallService.EXTRA_TASK_NAME, taskName)
|
|
438
|
-
putExtra(CallService.EXTRA_TASK_DATA, Bundle())
|
|
439
|
-
putExtra(CallService.EXTRA_TASK_TIMEOUT, timeout.toLong())
|
|
440
|
-
}
|
|
441
|
-
.also { ContextCompat.startForegroundService(reactApplicationContext, it) }
|
|
442
|
-
} catch (e: Exception) {
|
|
443
|
-
Log.e(TAG, "[module] startBackgroundTaskAutomatically: Failed to start foreground service: ${e.message}", e)
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
376
|
private fun executeServiceAction(callId: String, action: CallAction, promise: Promise) {
|
|
448
377
|
debugLog(TAG, "[module] executeServiceAction: Executing service action: $action")
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
} else {
|
|
455
|
-
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)
|
|
456
383
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
debugLog(TAG, "executeServiceAction: Service binding, queueing action")
|
|
460
|
-
promise.reject(
|
|
461
|
-
"SERVICE_BINDING",
|
|
462
|
-
"Service is connecting, please try again in a moment"
|
|
463
|
-
)
|
|
464
|
-
}
|
|
465
|
-
BindingState.UNBOUND -> {
|
|
466
|
-
promise.reject(
|
|
467
|
-
"SERVICE_NOT_CONNECTED",
|
|
468
|
-
"Service not connected. Call may not be active."
|
|
469
|
-
)
|
|
470
|
-
}
|
|
471
|
-
}
|
|
384
|
+
.also { reactApplicationContext.startService(it) }
|
|
385
|
+
.also { promise.resolve(true) }
|
|
472
386
|
}
|
|
473
387
|
|
|
474
388
|
private fun sendJSEvent(eventName: String, params: WritableMap? = null) {
|
|
@@ -507,229 +421,73 @@ class CallingxModuleImpl(
|
|
|
507
421
|
}
|
|
508
422
|
}
|
|
509
423
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
addAction(CALL_ANSWERED_ACTION)
|
|
515
|
-
addAction(CALL_ACTIVE_ACTION)
|
|
516
|
-
addAction(CALL_INACTIVE_ACTION)
|
|
517
|
-
addAction(CALL_MUTED_ACTION)
|
|
518
|
-
addAction(CALL_ENDPOINT_CHANGED_ACTION)
|
|
519
|
-
addAction(CALL_END_ACTION)
|
|
520
|
-
addAction(CALL_REGISTRATION_FAILED_ACTION)
|
|
521
|
-
addAction(SERVICE_READY_ACTION)
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
private fun bindToServiceIfNeeded() {
|
|
525
|
-
when (bindingState) {
|
|
526
|
-
BindingState.BOUND -> {
|
|
527
|
-
debugLog(TAG, "[module] bindToServiceIfNeeded: Already bound")
|
|
528
|
-
return
|
|
529
|
-
}
|
|
530
|
-
BindingState.BINDING -> {
|
|
531
|
-
debugLog(TAG, "[module] bindToServiceIfNeeded: Already binding")
|
|
532
|
-
return
|
|
533
|
-
}
|
|
534
|
-
BindingState.UNBOUND -> {
|
|
535
|
-
debugLog(TAG, "[module] bindToServiceIfNeeded: Attempting to bind")
|
|
536
|
-
val intent = Intent(reactApplicationContext, CallService::class.java)
|
|
537
|
-
try {
|
|
538
|
-
val success =
|
|
539
|
-
reactApplicationContext.bindService(
|
|
540
|
-
intent,
|
|
541
|
-
serviceConnection,
|
|
542
|
-
Context.BIND_AUTO_CREATE or Context.BIND_IMPORTANT
|
|
543
|
-
)
|
|
544
|
-
if (success) {
|
|
545
|
-
bindingState = BindingState.BINDING
|
|
546
|
-
debugLog(TAG, "[module] bindToServiceIfNeeded: Bind request successful")
|
|
547
|
-
} else {
|
|
548
|
-
Log.e(TAG, "[module] bindToServiceIfNeeded: Bind request failed")
|
|
549
|
-
bindingState = BindingState.UNBOUND
|
|
550
|
-
}
|
|
551
|
-
} catch (e: Exception) {
|
|
552
|
-
Log.e(TAG, "[module] bindToServiceIfNeeded: Exception during bind", e)
|
|
553
|
-
bindingState = BindingState.UNBOUND
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
private fun unbindServiceSafely() {
|
|
560
|
-
debugLog(TAG, "[module] unbindServiceSafely: Unbinding service")
|
|
561
|
-
if (bindingState == BindingState.BOUND || bindingState == BindingState.BINDING) {
|
|
562
|
-
try {
|
|
563
|
-
reactApplicationContext.unbindService(serviceConnection)
|
|
564
|
-
debugLog(TAG, "[module] unbindServiceSafely: Successfully unbound")
|
|
565
|
-
} catch (e: IllegalArgumentException) {
|
|
566
|
-
Log.w(
|
|
567
|
-
TAG,
|
|
568
|
-
"[module] unbindServiceSafely: Service not registered or already unbound"
|
|
569
|
-
)
|
|
570
|
-
} catch (e: Exception) {
|
|
571
|
-
Log.e(TAG, "[module] unbindServiceSafely: Error unbinding service", e)
|
|
572
|
-
} finally {
|
|
573
|
-
bindingState = BindingState.UNBOUND
|
|
574
|
-
callService = null
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
424
|
+
override fun onCallEvent(event: CallEvent) {
|
|
425
|
+
val action = event.action
|
|
426
|
+
val extras = event.extras
|
|
427
|
+
val callId = extras.getString(EXTRA_CALL_ID)
|
|
578
428
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
val success =
|
|
583
|
-
reactApplicationContext.bindService(
|
|
584
|
-
intent,
|
|
585
|
-
serviceConnection,
|
|
586
|
-
0 // No flags - only bind if service exists
|
|
587
|
-
)
|
|
588
|
-
if (success) {
|
|
589
|
-
bindingState = BindingState.BINDING
|
|
590
|
-
debugLog(TAG, "[module] checkForExistingService: Service exists, binding")
|
|
591
|
-
} else {
|
|
592
|
-
debugLog(TAG, "[module] checkForExistingService: No existing service")
|
|
593
|
-
}
|
|
594
|
-
} catch (e: Exception) {
|
|
595
|
-
Log.e(TAG, "[module] checkForExistingService: Error checking for service", e)
|
|
429
|
+
val params = Arguments.createMap()
|
|
430
|
+
if (callId != null) {
|
|
431
|
+
params.putString("callId", callId)
|
|
596
432
|
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
private inner class CallEventBroadcastReceiver : BroadcastReceiver() {
|
|
600
|
-
override fun onReceive(context: Context, intent: Intent) {
|
|
601
|
-
val action = intent.action
|
|
602
|
-
val callId = intent.getStringExtra(EXTRA_CALL_ID)
|
|
603
433
|
|
|
604
|
-
|
|
605
|
-
|
|
434
|
+
when (action) {
|
|
435
|
+
CALL_REGISTERED_ACTION -> {
|
|
436
|
+
sendJSEvent("didReceiveStartCallAction", params)
|
|
437
|
+
if (callId != null) {
|
|
438
|
+
CallRegistrationStore.onRegistrationSuccess(callId)
|
|
439
|
+
}
|
|
606
440
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
if (action == SERVICE_READY_ACTION) {
|
|
614
|
-
debugLog(TAG, "[module] onReceive: Service is ready, initiating binding, isHeadlessTaskRegistered: $isHeadlessTaskRegistered")
|
|
615
|
-
bindToServiceIfNeeded()
|
|
616
|
-
startBackgroundTaskAutomatically(HEADLESS_TASK_NAME, 0L)
|
|
617
|
-
return
|
|
441
|
+
CALL_REGISTERED_INCOMING_ACTION -> {
|
|
442
|
+
if (callId != null) {
|
|
443
|
+
CallRegistrationStore.onRegistrationSuccess(callId)
|
|
444
|
+
}
|
|
445
|
+
sendJSEvent("didDisplayIncomingCall", params)
|
|
618
446
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
447
|
+
CALL_REGISTRATION_FAILED_ACTION -> {
|
|
448
|
+
if (callId != null) {
|
|
449
|
+
CallRegistrationStore.onRegistrationFailed(callId)
|
|
450
|
+
}
|
|
623
451
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
sendJSEvent("didReceiveStartCallAction", params)
|
|
452
|
+
CALL_ANSWERED_ACTION -> {
|
|
453
|
+
if (extras.containsKey(EXTRA_SOURCE)) {
|
|
454
|
+
params.putString("source", extras.getString(EXTRA_SOURCE))
|
|
628
455
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
sendJSEvent("didDisplayIncomingCall", params)
|
|
456
|
+
sendJSEvent("answerCall", params)
|
|
457
|
+
}
|
|
458
|
+
CALL_END_ACTION -> {
|
|
459
|
+
val source = extras.getString(EXTRA_SOURCE)
|
|
460
|
+
if (source != null) {
|
|
461
|
+
params.putString("source", source)
|
|
638
462
|
}
|
|
639
|
-
|
|
463
|
+
if (source == "app") {
|
|
640
464
|
if (callId != null) {
|
|
641
|
-
|
|
642
|
-
}
|
|
643
|
-
// Reject the pending displayIncomingCall promise for this callId
|
|
644
|
-
if (callId != null) {
|
|
645
|
-
synchronized(pendingDisplayPromises) {
|
|
646
|
-
pendingTimeouts.remove(callId)?.let { mainHandler.removeCallbacks(it) }
|
|
647
|
-
pendingDisplayPromises.remove(callId)?.reject(
|
|
648
|
-
"REGISTRATION_FAILED",
|
|
649
|
-
"Failed to register call with telecom: $callId"
|
|
650
|
-
)
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
CALL_ANSWERED_ACTION -> {
|
|
655
|
-
if (intent.hasExtra(EXTRA_SOURCE)) {
|
|
656
|
-
params.putString("source", intent.getStringExtra(EXTRA_SOURCE))
|
|
657
|
-
}
|
|
658
|
-
sendJSEvent("answerCall", params)
|
|
659
|
-
}
|
|
660
|
-
CALL_END_ACTION -> {
|
|
661
|
-
val source = intent.getStringExtra(EXTRA_SOURCE)
|
|
662
|
-
if (source != null) {
|
|
663
|
-
params.putString("source", source)
|
|
664
|
-
}
|
|
665
|
-
if (source == "app") {
|
|
666
|
-
if (callId != null) {
|
|
667
|
-
//we stop tracking the call only when it was handled by the app
|
|
668
|
-
//in case source is "sys" we still need to know that call is tracked, otherwise we'll unable to end call from js side
|
|
669
|
-
trackedCallIds.remove(callId)
|
|
670
|
-
}
|
|
671
|
-
// means the call was disconnected, we're ready to unbind the service
|
|
672
|
-
unbindServiceSafely()
|
|
465
|
+
CallRegistrationStore.removeTrackedCall(callId)
|
|
673
466
|
}
|
|
674
|
-
params.putString("cause", intent.getStringExtra(EXTRA_DISCONNECT_CAUSE))
|
|
675
|
-
sendJSEvent("endCall", params)
|
|
676
|
-
}
|
|
677
|
-
CALL_INACTIVE_ACTION -> {
|
|
678
|
-
params.putBoolean("hold", true)
|
|
679
|
-
sendJSEvent("didToggleHoldCallAction", params)
|
|
680
467
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
468
|
+
params.putString("cause", extras.getString(EXTRA_DISCONNECT_CAUSE))
|
|
469
|
+
sendJSEvent("endCall", params)
|
|
470
|
+
}
|
|
471
|
+
CALL_INACTIVE_ACTION -> {
|
|
472
|
+
params.putBoolean("hold", true)
|
|
473
|
+
sendJSEvent("didToggleHoldCallAction", params)
|
|
474
|
+
}
|
|
475
|
+
CALL_ACTIVE_ACTION -> {
|
|
476
|
+
params.putBoolean("hold", false)
|
|
477
|
+
sendJSEvent("didToggleHoldCallAction", params)
|
|
478
|
+
}
|
|
479
|
+
CALL_MUTED_ACTION -> {
|
|
480
|
+
if (extras.containsKey(EXTRA_MUTED)) {
|
|
481
|
+
params.putBoolean("muted", extras.getBoolean(EXTRA_MUTED, false))
|
|
690
482
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
483
|
+
sendJSEvent("didPerformSetMutedCallAction", params)
|
|
484
|
+
}
|
|
485
|
+
CALL_ENDPOINT_CHANGED_ACTION -> {
|
|
486
|
+
if (extras.containsKey(EXTRA_AUDIO_ENDPOINT)) {
|
|
487
|
+
params.putString("output", extras.getString(EXTRA_AUDIO_ENDPOINT))
|
|
696
488
|
}
|
|
489
|
+
sendJSEvent("didChangeAudioRoute", params)
|
|
697
490
|
}
|
|
698
491
|
}
|
|
699
492
|
}
|
|
700
|
-
|
|
701
|
-
private val serviceConnection =
|
|
702
|
-
object : ServiceConnection {
|
|
703
|
-
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
|
704
|
-
debugLog(TAG, "[module] onServiceConnected: Service connected")
|
|
705
|
-
val binder = service as? CallService.CallServiceBinder
|
|
706
|
-
callService = binder?.getService()
|
|
707
|
-
bindingState = BindingState.BOUND
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
override fun onServiceDisconnected(name: ComponentName?) {
|
|
711
|
-
debugLog(TAG, "onServiceDisconnected: Service disconnected unexpectedly")
|
|
712
|
-
callService = null
|
|
713
|
-
bindingState = BindingState.UNBOUND
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
override fun onBindingDied(name: ComponentName?) {
|
|
717
|
-
Log.e(TAG, "[module] onBindingDied: Service binding died")
|
|
718
|
-
callService = null
|
|
719
|
-
bindingState = BindingState.UNBOUND
|
|
720
|
-
|
|
721
|
-
// Must unbind to clean up the dead binding
|
|
722
|
-
try {
|
|
723
|
-
reactApplicationContext.unbindService(this)
|
|
724
|
-
} catch (e: Exception) {
|
|
725
|
-
Log.w(TAG, "[module] onBindingDied: Error unbinding dead connection", e)
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
override fun onNullBinding(name: ComponentName?) {
|
|
730
|
-
Log.e(TAG, "[module] onNullBinding: Service returned null binding")
|
|
731
|
-
bindingState = BindingState.UNBOUND
|
|
732
|
-
callService = null
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
493
|
}
|