@stream-io/react-native-callingx 0.1.0-beta.7 → 0.1.1-beta.0
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 +176 -0
- package/android/src/main/java/io/getstream/rn/callingx/CallService.kt +302 -80
- package/android/src/main/java/io/getstream/rn/callingx/CallingxModuleImpl.kt +176 -191
- 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 +196 -46
- 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 +13 -10
- package/dist/module/CallingxModule.js.map +1 -1
- package/dist/module/spec/NativeCallingx.js.map +1 -1
- package/dist/module/utils/constants.js +24 -13
- package/dist/module/utils/constants.js.map +1 -1
- package/dist/typescript/src/CallingxModule.d.ts +3 -0
- package/dist/typescript/src/CallingxModule.d.ts.map +1 -1
- package/dist/typescript/src/spec/NativeCallingx.d.ts +7 -1
- package/dist/typescript/src/spec/NativeCallingx.d.ts.map +1 -1
- package/dist/typescript/src/types.d.ts +31 -0
- package/dist/typescript/src/types.d.ts.map +1 -1
- package/dist/typescript/src/utils/constants.d.ts +1 -1
- 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 +14 -10
- package/src/spec/NativeCallingx.ts +10 -3
- package/src/types.ts +34 -0
- package/src/utils/constants.ts +23 -9
- /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
|
@@ -8,9 +8,7 @@ import android.content.IntentFilter
|
|
|
8
8
|
import android.content.ServiceConnection
|
|
9
9
|
import android.os.Build
|
|
10
10
|
import android.os.Bundle
|
|
11
|
-
import android.os.Handler
|
|
12
11
|
import android.os.IBinder
|
|
13
|
-
import android.os.Looper
|
|
14
12
|
import android.telecom.DisconnectCause
|
|
15
13
|
import android.util.Log
|
|
16
14
|
import androidx.core.content.ContextCompat
|
|
@@ -27,12 +25,12 @@ import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
|
27
25
|
import io.getstream.rn.callingx.model.CallAction
|
|
28
26
|
import io.getstream.rn.callingx.notifications.NotificationChannelsManager
|
|
29
27
|
import io.getstream.rn.callingx.notifications.NotificationsConfig
|
|
30
|
-
import
|
|
28
|
+
import io.getstream.rn.callingx.utils.SettingsStore
|
|
31
29
|
|
|
32
30
|
class CallingxModuleImpl(
|
|
33
31
|
private val reactApplicationContext: ReactApplicationContext,
|
|
34
32
|
private val eventEmitter: CallingxEventEmitterAdapter
|
|
35
|
-
) {
|
|
33
|
+
) : CallEventBus.Listener {
|
|
36
34
|
|
|
37
35
|
companion object {
|
|
38
36
|
const val TAG = "[Callingx] CallingxModule"
|
|
@@ -45,19 +43,20 @@ class CallingxModuleImpl(
|
|
|
45
43
|
const val EXTRA_AUDIO_ENDPOINT = "audio_endpoint"
|
|
46
44
|
const val EXTRA_SOURCE = "source"
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
const val
|
|
50
|
-
const val
|
|
51
|
-
|
|
52
|
-
const val CALL_INACTIVE_ACTION = "
|
|
53
|
-
const val CALL_ACTIVE_ACTION = "
|
|
54
|
-
const val CALL_MUTED_ACTION = "
|
|
55
|
-
const val CALL_ENDPOINT_CHANGED_ACTION = "
|
|
56
|
-
const val CALL_END_ACTION = "
|
|
57
|
-
const val CALL_REGISTRATION_FAILED_ACTION = "
|
|
46
|
+
// Action names must match intent-filter entries in AndroidManifest.xml
|
|
47
|
+
const val CALL_REGISTERED_ACTION = "io.getstream.CALL_REGISTERED"
|
|
48
|
+
const val CALL_REGISTERED_INCOMING_ACTION = "io.getstream.CALL_REGISTERED_INCOMING"
|
|
49
|
+
const val CALL_ANSWERED_ACTION = "io.getstream.CALL_ANSWERED"
|
|
50
|
+
const val CALL_INACTIVE_ACTION = "io.getstream.CALL_INACTIVE"
|
|
51
|
+
const val CALL_ACTIVE_ACTION = "io.getstream.CALL_ACTIVE"
|
|
52
|
+
const val CALL_MUTED_ACTION = "io.getstream.CALL_MUTED"
|
|
53
|
+
const val CALL_ENDPOINT_CHANGED_ACTION = "io.getstream.CALL_ENDPOINT_CHANGED"
|
|
54
|
+
const val CALL_END_ACTION = "io.getstream.CALL_END"
|
|
55
|
+
const val CALL_REGISTRATION_FAILED_ACTION = "io.getstream.CALL_REGISTRATION_FAILED"
|
|
56
|
+
const val CALL_OPTIMISTIC_ACCEPT_ACTION = "io.getstream.ACCEPT_CALL_OPTIMISTIC"
|
|
58
57
|
// Background task name
|
|
59
58
|
const val HEADLESS_TASK_NAME = "HandleCallBackgroundState"
|
|
60
|
-
const val SERVICE_READY_ACTION = "
|
|
59
|
+
const val SERVICE_READY_ACTION = "io.getstream.SERVICE_READY"
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
private enum class BindingState {
|
|
@@ -74,18 +73,8 @@ class CallingxModuleImpl(
|
|
|
74
73
|
private var canSendEvents = false
|
|
75
74
|
private var isHeadlessTaskRegistered = false
|
|
76
75
|
|
|
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
|
-
|
|
87
76
|
private val notificationChannelsManager = NotificationChannelsManager(reactApplicationContext)
|
|
88
|
-
private val
|
|
77
|
+
private val serviceReadyBroadcastReceiver = ServiceReadyBroadcastReceiver()
|
|
89
78
|
private val appStateListener =
|
|
90
79
|
object : LifecycleEventListener {
|
|
91
80
|
override fun onHostResume() {}
|
|
@@ -100,15 +89,20 @@ class CallingxModuleImpl(
|
|
|
100
89
|
}
|
|
101
90
|
|
|
102
91
|
init {
|
|
92
|
+
CallEventBus.subscribe(this)
|
|
93
|
+
|
|
103
94
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
104
95
|
reactApplicationContext.registerReceiver(
|
|
105
|
-
|
|
106
|
-
|
|
96
|
+
serviceReadyBroadcastReceiver,
|
|
97
|
+
getServiceReadyReceiverFilter(),
|
|
107
98
|
Context.RECEIVER_NOT_EXPORTED
|
|
108
99
|
)
|
|
109
100
|
} else {
|
|
110
101
|
@Suppress("UnspecifiedRegisterReceiverFlag")
|
|
111
|
-
reactApplicationContext.registerReceiver(
|
|
102
|
+
reactApplicationContext.registerReceiver(
|
|
103
|
+
serviceReadyBroadcastReceiver,
|
|
104
|
+
getServiceReadyReceiverFilter()
|
|
105
|
+
)
|
|
112
106
|
}
|
|
113
107
|
}
|
|
114
108
|
|
|
@@ -123,21 +117,24 @@ class CallingxModuleImpl(
|
|
|
123
117
|
fun invalidate() {
|
|
124
118
|
debugLog(TAG, "[module] invalidate: Invalidating module")
|
|
125
119
|
|
|
126
|
-
|
|
127
|
-
synchronized(pendingDisplayPromises) {
|
|
128
|
-
pendingTimeouts.values.forEach { mainHandler.removeCallbacks(it) }
|
|
129
|
-
pendingTimeouts.clear()
|
|
130
|
-
pendingDisplayPromises.clear()
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
trackedCallIds.clear()
|
|
120
|
+
CallRegistrationStore.clearAll()
|
|
134
121
|
unbindServiceSafely()
|
|
135
122
|
|
|
123
|
+
CallEventBus.unsubscribe(this)
|
|
124
|
+
|
|
136
125
|
reactApplicationContext.removeLifecycleEventListener(appStateListener)
|
|
137
|
-
reactApplicationContext.unregisterReceiver(
|
|
126
|
+
reactApplicationContext.unregisterReceiver(serviceReadyBroadcastReceiver)
|
|
138
127
|
isModuleInitialized = false
|
|
139
128
|
}
|
|
140
129
|
|
|
130
|
+
fun setShouldRejectCallWhenBusy(shouldReject: Boolean) {
|
|
131
|
+
debugLog(
|
|
132
|
+
TAG,
|
|
133
|
+
"[module] setShouldRejectCallWhenBusy: Updating rejectCallWhenBusy to $shouldReject"
|
|
134
|
+
)
|
|
135
|
+
SettingsStore.setShouldRejectCallWhenBusy(reactApplicationContext, shouldReject)
|
|
136
|
+
}
|
|
137
|
+
|
|
141
138
|
fun setupAndroid(options: ReadableMap) {
|
|
142
139
|
debugLog(TAG, "[module] setupAndroid: Setting up Android: $options")
|
|
143
140
|
val notificationsConfig =
|
|
@@ -145,6 +142,18 @@ class CallingxModuleImpl(
|
|
|
145
142
|
notificationChannelsManager.setNotificationsConfig(notificationsConfig)
|
|
146
143
|
notificationChannelsManager.createNotificationChannels()
|
|
147
144
|
|
|
145
|
+
val notificationTexts = options.getMap("notificationTexts")
|
|
146
|
+
if (notificationTexts != null) {
|
|
147
|
+
val acceptingText = notificationTexts.getString("accepting")
|
|
148
|
+
val rejectingText = notificationTexts.getString("rejecting")
|
|
149
|
+
debugLog(TAG, "[module] $acceptingText $rejectingText")
|
|
150
|
+
SettingsStore.setOptimisticTexts(
|
|
151
|
+
reactApplicationContext,
|
|
152
|
+
acceptingText,
|
|
153
|
+
rejectingText,
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
148
157
|
isModuleInitialized = true
|
|
149
158
|
}
|
|
150
159
|
|
|
@@ -152,13 +161,30 @@ class CallingxModuleImpl(
|
|
|
152
161
|
return notificationChannelsManager.getNotificationStatus().canPost
|
|
153
162
|
}
|
|
154
163
|
|
|
164
|
+
fun stopService(promise: Promise) {
|
|
165
|
+
debugLog(TAG, "[module] stopService: Stopping CallService explicitly from JS")
|
|
166
|
+
try {
|
|
167
|
+
Intent(reactApplicationContext, CallService::class.java)
|
|
168
|
+
.apply { action = CallService.ACTION_STOP_SERVICE }
|
|
169
|
+
.also { reactApplicationContext.startService(it) }
|
|
170
|
+
|
|
171
|
+
promise.resolve(true)
|
|
172
|
+
} catch (e: Exception) {
|
|
173
|
+
Log.e(TAG, "[module] stopService: Failed to stop service: ${e.message}", e)
|
|
174
|
+
promise.reject("STOP_SERVICE_ERROR", e.message, e)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
155
178
|
fun getInitialEvents(): WritableArray {
|
|
156
|
-
|
|
157
|
-
|
|
179
|
+
CallEventBus.drainPendingEvents().forEach { onCallEvent(it) }
|
|
180
|
+
|
|
181
|
+
// NOTE: writable native array can be consumed only once, think of getting rid from clear
|
|
182
|
+
// event and clear it immediately after getting initial events
|
|
158
183
|
val events = delayedEvents
|
|
159
184
|
debugLog(TAG, "[module] getInitialEvents: Getting initial events: $events")
|
|
160
185
|
delayedEvents = WritableNativeArray()
|
|
161
186
|
canSendEvents = true
|
|
187
|
+
CallEventBus.markJsReady()
|
|
162
188
|
return events
|
|
163
189
|
}
|
|
164
190
|
|
|
@@ -184,30 +210,7 @@ class CallingxModuleImpl(
|
|
|
184
210
|
return
|
|
185
211
|
}
|
|
186
212
|
|
|
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
|
-
}
|
|
213
|
+
CallRegistrationStore.trackCallRegistration(callId, promise)
|
|
211
214
|
|
|
212
215
|
try {
|
|
213
216
|
startCallService(
|
|
@@ -220,12 +223,12 @@ class CallingxModuleImpl(
|
|
|
220
223
|
)
|
|
221
224
|
} catch (e: Exception) {
|
|
222
225
|
Log.e(TAG, "[module] displayIncomingCall: Failed to start foreground service: ${e.message}", e)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
226
|
+
CallRegistrationStore.reportRegistrationFail(
|
|
227
|
+
callId,
|
|
228
|
+
"START_FOREGROUND_SERVICE_ERROR",
|
|
229
|
+
e.message,
|
|
230
|
+
e
|
|
231
|
+
)
|
|
229
232
|
}
|
|
230
233
|
}
|
|
231
234
|
|
|
@@ -256,7 +259,7 @@ class CallingxModuleImpl(
|
|
|
256
259
|
return
|
|
257
260
|
}
|
|
258
261
|
|
|
259
|
-
|
|
262
|
+
CallRegistrationStore.trackCallRegistration(callId, promise)
|
|
260
263
|
|
|
261
264
|
try {
|
|
262
265
|
startCallService(
|
|
@@ -267,11 +270,14 @@ class CallingxModuleImpl(
|
|
|
267
270
|
hasVideo,
|
|
268
271
|
displayOptions
|
|
269
272
|
)
|
|
270
|
-
promise.resolve(true)
|
|
271
273
|
} catch (e: Exception) {
|
|
272
274
|
Log.e(TAG, "[module] startCall: Failed to start foreground service: ${e.message}", e)
|
|
273
|
-
|
|
274
|
-
|
|
275
|
+
CallRegistrationStore.reportRegistrationFail(
|
|
276
|
+
callId,
|
|
277
|
+
"START_FOREGROUND_SERVICE_ERROR",
|
|
278
|
+
e.message,
|
|
279
|
+
e
|
|
280
|
+
)
|
|
275
281
|
}
|
|
276
282
|
}
|
|
277
283
|
|
|
@@ -307,28 +313,24 @@ class CallingxModuleImpl(
|
|
|
307
313
|
|
|
308
314
|
fun endCallWithReason(callId: String, reason: Double, promise: Promise) {
|
|
309
315
|
debugLog(TAG, "[module] endCallWithReason: Ending call: $callId, $reason")
|
|
310
|
-
|
|
316
|
+
CallRegistrationStore.removeTrackedCall(callId)
|
|
311
317
|
val action = CallAction.Disconnect(DisconnectCause(reason.toInt()))
|
|
312
318
|
executeServiceAction(callId, action, promise)
|
|
313
319
|
}
|
|
314
320
|
|
|
315
321
|
fun endCall(callId: String, promise: Promise) {
|
|
316
322
|
debugLog(TAG, "[module] endCall: Ending call: $callId")
|
|
317
|
-
|
|
323
|
+
CallRegistrationStore.removeTrackedCall(callId)
|
|
318
324
|
val action = CallAction.Disconnect(DisconnectCause(DisconnectCause.LOCAL))
|
|
319
325
|
executeServiceAction(callId, action, promise)
|
|
320
326
|
}
|
|
321
327
|
|
|
322
328
|
fun isCallTracked(callId: String): Boolean {
|
|
323
|
-
|
|
324
|
-
debugLog(TAG, "[module] isCallTracked: Is call tracked: $isTracked")
|
|
325
|
-
return isTracked
|
|
329
|
+
return CallRegistrationStore.isCallTracked(callId)
|
|
326
330
|
}
|
|
327
331
|
|
|
328
332
|
fun hasRegisteredCall(): Boolean {
|
|
329
|
-
|
|
330
|
-
debugLog(TAG, "[module] hasRegisteredCall: Has registered call: $hasRegisteredCall")
|
|
331
|
-
return hasRegisteredCall
|
|
333
|
+
return CallRegistrationStore.hasRegisteredCall()
|
|
332
334
|
}
|
|
333
335
|
|
|
334
336
|
fun setMutedCall(callId: String, isMuted: Boolean, promise: Promise) {
|
|
@@ -352,12 +354,12 @@ class CallingxModuleImpl(
|
|
|
352
354
|
putExtra(CallService.EXTRA_TASK_DATA, Bundle())
|
|
353
355
|
putExtra(CallService.EXTRA_TASK_TIMEOUT, timeout.toLong())
|
|
354
356
|
}
|
|
355
|
-
.also {
|
|
357
|
+
.also { reactApplicationContext.startService(it) }
|
|
356
358
|
|
|
357
359
|
promise.resolve(true)
|
|
358
360
|
} catch (e: Exception) {
|
|
359
|
-
Log.e(TAG, "[module] startBackgroundTask: Failed to start
|
|
360
|
-
promise.reject("
|
|
361
|
+
Log.e(TAG, "[module] startBackgroundTask: Failed to start service: ${e.message}", e)
|
|
362
|
+
promise.reject("START_SERVICE_ERROR", e.message, e)
|
|
361
363
|
}
|
|
362
364
|
}
|
|
363
365
|
|
|
@@ -368,13 +370,13 @@ class CallingxModuleImpl(
|
|
|
368
370
|
this.action = CallService.ACTION_STOP_BACKGROUND_TASK
|
|
369
371
|
putExtra(CallService.EXTRA_TASK_NAME, taskName)
|
|
370
372
|
}
|
|
371
|
-
.also {
|
|
373
|
+
.also { reactApplicationContext.startService(it) }
|
|
372
374
|
|
|
373
375
|
isHeadlessTaskRegistered = false
|
|
374
376
|
promise.resolve(true)
|
|
375
377
|
} catch (e: Exception) {
|
|
376
|
-
Log.e(TAG, "[module] stopBackgroundTask: Failed to start
|
|
377
|
-
promise.reject("
|
|
378
|
+
Log.e(TAG, "[module] stopBackgroundTask: Failed to start service: ${e.message}", e)
|
|
379
|
+
promise.reject("START_SERVICE_ERROR", e.message, e)
|
|
378
380
|
}
|
|
379
381
|
}
|
|
380
382
|
|
|
@@ -383,13 +385,13 @@ class CallingxModuleImpl(
|
|
|
383
385
|
isHeadlessTaskRegistered = true
|
|
384
386
|
}
|
|
385
387
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
388
|
+
|
|
389
|
+
fun fulfillAnswerCallAction(callId: String, didFail: Boolean) {
|
|
390
|
+
// no-op: Android Telecom doesn't require explicit action fulfillment
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
fun fulfillEndCallAction(callId: String, didFail: Boolean) {
|
|
394
|
+
// no-op: Android Telecom doesn't require explicit action fulfillment
|
|
393
395
|
}
|
|
394
396
|
|
|
395
397
|
fun log(message: String, level: String) {
|
|
@@ -438,9 +440,9 @@ class CallingxModuleImpl(
|
|
|
438
440
|
putExtra(CallService.EXTRA_TASK_DATA, Bundle())
|
|
439
441
|
putExtra(CallService.EXTRA_TASK_TIMEOUT, timeout.toLong())
|
|
440
442
|
}
|
|
441
|
-
.also {
|
|
443
|
+
.also { reactApplicationContext.startService(it) }
|
|
442
444
|
} catch (e: Exception) {
|
|
443
|
-
Log.e(TAG, "[module] startBackgroundTaskAutomatically: Failed to start
|
|
445
|
+
Log.e(TAG, "[module] startBackgroundTaskAutomatically: Failed to start service: ${e.message}", e)
|
|
444
446
|
}
|
|
445
447
|
}
|
|
446
448
|
|
|
@@ -507,17 +509,8 @@ class CallingxModuleImpl(
|
|
|
507
509
|
}
|
|
508
510
|
}
|
|
509
511
|
|
|
510
|
-
private fun
|
|
512
|
+
private fun getServiceReadyReceiverFilter(): IntentFilter =
|
|
511
513
|
IntentFilter().apply {
|
|
512
|
-
addAction(CALL_REGISTERED_ACTION)
|
|
513
|
-
addAction(CALL_REGISTERED_INCOMING_ACTION)
|
|
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
514
|
addAction(SERVICE_READY_ACTION)
|
|
522
515
|
}
|
|
523
516
|
|
|
@@ -596,104 +589,96 @@ class CallingxModuleImpl(
|
|
|
596
589
|
}
|
|
597
590
|
}
|
|
598
591
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
if (action == null) {
|
|
605
|
-
return
|
|
606
|
-
}
|
|
592
|
+
override fun onCallEvent(event: CallEvent) {
|
|
593
|
+
val action = event.action
|
|
594
|
+
val extras = event.extras
|
|
595
|
+
val callId = extras.getString(EXTRA_CALL_ID)
|
|
607
596
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
597
|
+
debugLog(
|
|
598
|
+
TAG,
|
|
599
|
+
"[module] onCallEvent: Received event: $action callId: $callId callService: ${callService != null}"
|
|
600
|
+
)
|
|
612
601
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
return
|
|
618
|
-
}
|
|
602
|
+
val params = Arguments.createMap()
|
|
603
|
+
if (callId != null) {
|
|
604
|
+
params.putString("callId", callId)
|
|
605
|
+
}
|
|
619
606
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
607
|
+
when (action) {
|
|
608
|
+
CALL_REGISTERED_ACTION -> {
|
|
609
|
+
sendJSEvent("didReceiveStartCallAction", params)
|
|
610
|
+
if (callId != null) {
|
|
611
|
+
CallRegistrationStore.onRegistrationSuccess(callId)
|
|
612
|
+
}
|
|
623
613
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
sendJSEvent("didReceiveStartCallAction", params)
|
|
614
|
+
CALL_REGISTERED_INCOMING_ACTION -> {
|
|
615
|
+
if (callId != null) {
|
|
616
|
+
CallRegistrationStore.onRegistrationSuccess(callId)
|
|
628
617
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
pendingDisplayPromises.remove(callId)?.resolve(true)
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
sendJSEvent("didDisplayIncomingCall", params)
|
|
618
|
+
sendJSEvent("didDisplayIncomingCall", params)
|
|
619
|
+
}
|
|
620
|
+
CALL_REGISTRATION_FAILED_ACTION -> {
|
|
621
|
+
if (callId != null) {
|
|
622
|
+
CallRegistrationStore.onRegistrationFailed(callId)
|
|
638
623
|
}
|
|
639
|
-
|
|
640
|
-
|
|
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
|
-
}
|
|
624
|
+
}
|
|
625
|
+
CALL_ANSWERED_ACTION -> {
|
|
626
|
+
if (extras.containsKey(EXTRA_SOURCE)) {
|
|
627
|
+
params.putString("source", extras.getString(EXTRA_SOURCE))
|
|
653
628
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
629
|
+
sendJSEvent("answerCall", params)
|
|
630
|
+
}
|
|
631
|
+
CALL_END_ACTION -> {
|
|
632
|
+
val source = extras.getString(EXTRA_SOURCE)
|
|
633
|
+
if (source != null) {
|
|
634
|
+
params.putString("source", source)
|
|
659
635
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
params.putString("source", source)
|
|
636
|
+
if (source == "app") {
|
|
637
|
+
if (callId != null) {
|
|
638
|
+
CallRegistrationStore.removeTrackedCall(callId)
|
|
664
639
|
}
|
|
665
|
-
|
|
666
|
-
|
|
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
|
|
640
|
+
// Only unbind when no more calls are tracked
|
|
641
|
+
if (!CallRegistrationStore.hasRegisteredCall()) {
|
|
672
642
|
unbindServiceSafely()
|
|
673
643
|
}
|
|
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
644
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
645
|
+
params.putString("cause", extras.getString(EXTRA_DISCONNECT_CAUSE))
|
|
646
|
+
sendJSEvent("endCall", params)
|
|
647
|
+
}
|
|
648
|
+
CALL_INACTIVE_ACTION -> {
|
|
649
|
+
params.putBoolean("hold", true)
|
|
650
|
+
sendJSEvent("didToggleHoldCallAction", params)
|
|
651
|
+
}
|
|
652
|
+
CALL_ACTIVE_ACTION -> {
|
|
653
|
+
params.putBoolean("hold", false)
|
|
654
|
+
sendJSEvent("didToggleHoldCallAction", params)
|
|
655
|
+
}
|
|
656
|
+
CALL_MUTED_ACTION -> {
|
|
657
|
+
if (extras.containsKey(EXTRA_MUTED)) {
|
|
658
|
+
params.putBoolean("muted", extras.getBoolean(EXTRA_MUTED, false))
|
|
690
659
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
660
|
+
sendJSEvent("didPerformSetMutedCallAction", params)
|
|
661
|
+
}
|
|
662
|
+
CALL_ENDPOINT_CHANGED_ACTION -> {
|
|
663
|
+
if (extras.containsKey(EXTRA_AUDIO_ENDPOINT)) {
|
|
664
|
+
params.putString("output", extras.getString(EXTRA_AUDIO_ENDPOINT))
|
|
696
665
|
}
|
|
666
|
+
sendJSEvent("didChangeAudioRoute", params)
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
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)
|
|
697
682
|
}
|
|
698
683
|
}
|
|
699
684
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
package io.getstream.rn.callingx
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import com.google.firebase.messaging.RemoteMessage
|
|
5
|
+
import io.invertase.firebase.messaging.ReactNativeFirebaseMessagingService
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extends React Native Firebase's messaging service to start [CallService] when a
|
|
9
|
+
* data message contains "stream" (e.g. incoming call push), then delegates to the
|
|
10
|
+
* parent so setBackgroundMessageHandler() still runs in JS.
|
|
11
|
+
*
|
|
12
|
+
* Only compiled when the app has @react-native-firebase/app and @react-native-firebase/messaging
|
|
13
|
+
* as dependencies. The app must remove the default [io.invertase.firebase.messaging.ReactNativeFirebaseMessagingService] from
|
|
14
|
+
* the merged manifest so this service is the single FCM handler
|
|
15
|
+
*/
|
|
16
|
+
@SuppressLint("MissingFirebaseInstanceTokenRefresh")
|
|
17
|
+
class StreamMessagingService : ReactNativeFirebaseMessagingService() {
|
|
18
|
+
|
|
19
|
+
companion object {
|
|
20
|
+
const val TAG = "[Callingx] StreamMessagingService"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
|
24
|
+
val data = remoteMessage.data
|
|
25
|
+
debugLog(TAG, "onMessageReceived data = $data")
|
|
26
|
+
|
|
27
|
+
val isSupportedStreamVideoCallRing =
|
|
28
|
+
data["sender"] == "stream.video" && data["type"] == "call.ring"
|
|
29
|
+
|
|
30
|
+
if (isSupportedStreamVideoCallRing) {
|
|
31
|
+
val callCid = data["call_cid"]
|
|
32
|
+
if (callCid.isNullOrEmpty()) {
|
|
33
|
+
debugLog(
|
|
34
|
+
TAG,
|
|
35
|
+
"missing call_cid for call.ring, skipping CallService start",
|
|
36
|
+
)
|
|
37
|
+
} else {
|
|
38
|
+
CallService.startIncomingCallFromPush(applicationContext, data)
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
debugLog(TAG, "sender or type is not supported, skipping CallService start")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Let React Native Firebase continue its normal processing so
|
|
45
|
+
// setBackgroundMessageHandler() still runs in JS.
|
|
46
|
+
super.onMessageReceived(remoteMessage)
|
|
47
|
+
}
|
|
48
|
+
}
|