@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
|
@@ -19,17 +19,26 @@ import kotlinx.coroutines.cancel
|
|
|
19
19
|
import kotlinx.coroutines.channels.Channel
|
|
20
20
|
import kotlinx.coroutines.flow.Flow
|
|
21
21
|
import kotlinx.coroutines.flow.consumeAsFlow
|
|
22
|
-
import kotlinx.coroutines.flow.drop
|
|
23
22
|
import kotlinx.coroutines.flow.launchIn
|
|
24
23
|
import kotlinx.coroutines.flow.onEach
|
|
25
|
-
import kotlinx.coroutines.flow.scan
|
|
26
24
|
import kotlinx.coroutines.launch
|
|
27
25
|
import kotlinx.coroutines.sync.withLock
|
|
26
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
27
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* Per-call flags tracking whether an action was initiated by the app (self) or by the system.
|
|
31
|
+
*/
|
|
32
|
+
private data class CallActionFlags(
|
|
33
|
+
val isSelfAnswered: AtomicBoolean = AtomicBoolean(false),
|
|
34
|
+
val isSelfDisconnected: AtomicBoolean = AtomicBoolean(false),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The central repository that keeps track of calls and allows to register new ones.
|
|
31
39
|
*
|
|
32
40
|
* This class contains the main logic to integrate with Telecom SDK.
|
|
41
|
+
* Multiple calls can be registered simultaneously — each gets its own [CallControlScope].
|
|
33
42
|
*
|
|
34
43
|
* @see registerCall
|
|
35
44
|
*/
|
|
@@ -40,11 +49,15 @@ class TelecomCallRepository(context: Context) : CallRepository(context) {
|
|
|
40
49
|
private const val TAG = "[Callingx] TelecomCallRepository"
|
|
41
50
|
}
|
|
42
51
|
|
|
43
|
-
|
|
52
|
+
@Volatile
|
|
53
|
+
private var isReleased: Boolean = false
|
|
54
|
+
|
|
55
|
+
private var observeCallsJob: Job? = null
|
|
44
56
|
|
|
45
57
|
private val callsManager: CallsManager
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
|
|
59
|
+
/** Per-call action-source flags, keyed by callId. */
|
|
60
|
+
private val actionFlags = ConcurrentHashMap<String, CallActionFlags>()
|
|
48
61
|
|
|
49
62
|
init {
|
|
50
63
|
val capabilities =
|
|
@@ -62,26 +75,34 @@ class TelecomCallRepository(context: Context) : CallRepository(context) {
|
|
|
62
75
|
override fun setListener(listener: Listener?) {
|
|
63
76
|
this._listener = listener
|
|
64
77
|
|
|
65
|
-
|
|
66
|
-
|
|
78
|
+
observeCallsJob?.cancel()
|
|
79
|
+
observeCallsJob = observeCalls()
|
|
67
80
|
}
|
|
68
81
|
|
|
69
82
|
override fun release() {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
83
|
+
if (isReleased) {
|
|
84
|
+
debugLog(TAG, "[repository] release: Already released, ignoring")
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
isReleased = true
|
|
88
|
+
|
|
89
|
+
// Disconnect all active calls
|
|
90
|
+
val currentCalls = _calls.value
|
|
91
|
+
for ((callId, call) in currentCalls) {
|
|
92
|
+
call.processAction(CallAction.Disconnect(DisconnectCause(DisconnectCause.LOCAL)))
|
|
73
93
|
}
|
|
74
|
-
|
|
94
|
+
_calls.value = emptyMap()
|
|
95
|
+
actionFlags.clear()
|
|
75
96
|
|
|
76
|
-
|
|
77
|
-
|
|
97
|
+
observeCallsJob?.cancel()
|
|
98
|
+
observeCallsJob = null
|
|
78
99
|
_listener = null
|
|
79
100
|
|
|
80
101
|
scope.cancel()
|
|
81
102
|
}
|
|
82
103
|
|
|
83
104
|
/**
|
|
84
|
-
* Register a new call with the provided attributes. Use the [
|
|
105
|
+
* Register a new call with the provided attributes. Use the [calls] StateFlow to receive
|
|
85
106
|
* status updates and process call related actions.
|
|
86
107
|
*/
|
|
87
108
|
override suspend fun registerCall(
|
|
@@ -92,89 +113,119 @@ class TelecomCallRepository(context: Context) : CallRepository(context) {
|
|
|
92
113
|
isVideo: Boolean,
|
|
93
114
|
displayOptions: Bundle?,
|
|
94
115
|
) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
116
|
+
if (isReleased) {
|
|
117
|
+
Log.w(
|
|
118
|
+
TAG,
|
|
119
|
+
"[repository] registerCall: Repository already released, ignoring registration for $callId"
|
|
120
|
+
)
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Hold the mutex only for the dedup check — release before entering the long-lived call scope
|
|
125
|
+
val attributes: CallAttributesCompat
|
|
126
|
+
val actionSource: Channel<CallAction>
|
|
127
|
+
val flags: CallActionFlags
|
|
99
128
|
|
|
100
129
|
registrationMutex.withLock {
|
|
101
|
-
|
|
102
|
-
|
|
130
|
+
debugLog(
|
|
131
|
+
TAG,
|
|
132
|
+
"[repository] registerCall: Starting registration - CallId: $callId, Name: $displayName, Address: $address, Incoming: $isIncoming"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
// Check if this specific call is already registered or registration is in progress
|
|
136
|
+
if (_calls.value.containsKey(callId)) {
|
|
103
137
|
Log.w(
|
|
104
138
|
TAG,
|
|
105
|
-
"[repository] registerCall: Call already registered, ignoring
|
|
139
|
+
"[repository] registerCall: Call $callId already registered, ignoring duplicate"
|
|
106
140
|
)
|
|
107
|
-
return
|
|
141
|
+
return
|
|
108
142
|
}
|
|
143
|
+
|
|
109
144
|
debugLog(
|
|
110
145
|
TAG,
|
|
111
|
-
"[repository] registerCall:
|
|
146
|
+
"[repository] registerCall: Call $callId not found in map, proceeding with registration"
|
|
112
147
|
)
|
|
113
148
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
149
|
+
attributes = createCallAttributes(displayName, address, isIncoming, isVideo)
|
|
150
|
+
actionSource = Channel<CallAction>()
|
|
151
|
+
flags = CallActionFlags()
|
|
152
|
+
actionFlags[callId] = flags
|
|
153
|
+
|
|
154
|
+
// Add call to the map early so that duplicate registrations are rejected
|
|
155
|
+
// and listeners are notified immediately. Actions sent via trySend() may arrive
|
|
156
|
+
// before the call scope starts collecting; in that case, they are dropped
|
|
157
|
+
// rather than buffered, which is acceptable because we explicitly handle
|
|
158
|
+
// pending actions in CallService/CallRegistrationStore.
|
|
159
|
+
val registeredCall = Call.Registered(
|
|
160
|
+
id = callId,
|
|
161
|
+
isPending = true,
|
|
162
|
+
isActive = false,
|
|
163
|
+
isOnHold = false,
|
|
164
|
+
callAttributes = attributes,
|
|
165
|
+
displayOptions = displayOptions,
|
|
166
|
+
isMuted = false,
|
|
167
|
+
errorCode = null,
|
|
168
|
+
currentCallEndpoint = null,
|
|
169
|
+
availableCallEndpoints = emptyList(),
|
|
170
|
+
actionSource = actionSource,
|
|
171
|
+
)
|
|
172
|
+
addCall(callId, registeredCall)
|
|
173
|
+
debugLog(TAG, "[repository] registerCall: Call $callId added to map in pending state")
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Register the call with Telecom and handle actions in the scope (mutex released)
|
|
177
|
+
try {
|
|
178
|
+
callsManager.addCall(
|
|
179
|
+
attributes,
|
|
180
|
+
onIsCallAnswered(callId, flags),
|
|
181
|
+
onIsCallDisconnected(callId, flags),
|
|
182
|
+
onIsCallActive(callId),
|
|
183
|
+
onIsCallInactive(callId)
|
|
184
|
+
) {
|
|
185
|
+
debugLog(
|
|
186
|
+
TAG,
|
|
187
|
+
"[repository] registerCall: Inside call scope for $callId, setting up call handlers"
|
|
188
|
+
)
|
|
130
189
|
|
|
131
|
-
|
|
132
|
-
|
|
190
|
+
// Call is now registered in Telecom — mark as no longer pending
|
|
191
|
+
updateCallById(callId) { copy(isPending = false) }
|
|
133
192
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
debugLog(
|
|
137
|
-
TAG,
|
|
138
|
-
"[repository] registerCall: Creating Registered call state with ID: $callId"
|
|
139
|
-
)
|
|
140
|
-
_currentCall.value =
|
|
141
|
-
Call.Registered(
|
|
142
|
-
id = callId,
|
|
143
|
-
isActive = false, // can we just register the call as active?
|
|
144
|
-
isOnHold = false,
|
|
145
|
-
callAttributes = attributes,
|
|
146
|
-
displayOptions = displayOptions,
|
|
147
|
-
isMuted = false,
|
|
148
|
-
errorCode = null,
|
|
149
|
-
currentCallEndpoint = null,
|
|
150
|
-
availableCallEndpoints = emptyList(),
|
|
151
|
-
actionSource = actionSource,
|
|
152
|
-
)
|
|
153
|
-
debugLog(TAG, "[repository] registerCall: Call state updated to Registered")
|
|
193
|
+
// Consume the actions to interact with the call inside the scope
|
|
194
|
+
launch { processCallActions(callId, flags, actionSource.consumeAsFlow()) }
|
|
154
195
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
196
|
+
launch {
|
|
197
|
+
currentCallEndpoint.collect {
|
|
198
|
+
updateCallById(callId) { copy(currentCallEndpoint = it) }
|
|
159
199
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
200
|
+
}
|
|
201
|
+
launch {
|
|
202
|
+
availableEndpoints.collect {
|
|
203
|
+
updateCallById(callId) { copy(availableCallEndpoints = it) }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
launch {
|
|
207
|
+
isMuted.collect {
|
|
208
|
+
updateCallById(callId) { copy(isMuted = it) }
|
|
164
209
|
}
|
|
165
|
-
launch { isMuted.collect { updateCurrentCall { copy(isMuted = it) } } }
|
|
166
210
|
}
|
|
167
|
-
debugLog(
|
|
168
|
-
TAG,
|
|
169
|
-
"[repository] registerCall: Call successfully registered with Telecom SDK"
|
|
170
|
-
)
|
|
171
|
-
} catch (e: Exception) {
|
|
172
|
-
Log.e(TAG, "[repository] registerCall: Error registering call", e)
|
|
173
|
-
throw e
|
|
174
|
-
} finally {
|
|
175
|
-
debugLog(TAG, "[repository] registerCall: Call scope ended, setting state to None")
|
|
176
|
-
_currentCall.value = Call.None
|
|
177
211
|
}
|
|
212
|
+
debugLog(
|
|
213
|
+
TAG,
|
|
214
|
+
"[repository] registerCall: Call $callId scope ended normally"
|
|
215
|
+
)
|
|
216
|
+
} catch (e: Exception) {
|
|
217
|
+
Log.e(TAG, "[repository] registerCall: Error registering call $callId", e)
|
|
218
|
+
throw e
|
|
219
|
+
} finally {
|
|
220
|
+
// Call lifecycle cleanup:
|
|
221
|
+
// - processCallActions(...) is launched in the CallControlScope, so its collector is
|
|
222
|
+
// cancelled automatically when the Telecom call scope finishes.
|
|
223
|
+
// - We then remove the call from the repository map and clear per-call flags.
|
|
224
|
+
// - The Call.actionSource channel is no longer referenced and can be garbage-collected;
|
|
225
|
+
// we do not explicitly close it because callers use trySend(), which never suspends.
|
|
226
|
+
debugLog(TAG, "[repository] registerCall: Cleaning up call $callId")
|
|
227
|
+
removeCall(callId)
|
|
228
|
+
actionFlags.remove(callId)
|
|
178
229
|
}
|
|
179
230
|
}
|
|
180
231
|
|
|
@@ -187,63 +238,77 @@ class TelecomCallRepository(context: Context) : CallRepository(context) {
|
|
|
187
238
|
) {
|
|
188
239
|
debugLog(
|
|
189
240
|
TAG,
|
|
190
|
-
"[repository] updateCall: Starting update - Name: $displayName, Address: $address, IsVideo: $isVideo"
|
|
241
|
+
"[repository] updateCall: Starting update - CallId: $callId, Name: $displayName, Address: $address, IsVideo: $isVideo"
|
|
191
242
|
)
|
|
192
243
|
super.updateCall(callId, displayName, address, isVideo, displayOptions)
|
|
193
244
|
}
|
|
194
245
|
|
|
195
|
-
private fun
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
previous
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
246
|
+
private fun observeCalls(): Job {
|
|
247
|
+
// Track previous state per call for diffing (only non-pending calls)
|
|
248
|
+
var previousCalls: Map<String, Call.Registered> = emptyMap()
|
|
249
|
+
|
|
250
|
+
return calls
|
|
251
|
+
.onEach { allCalls ->
|
|
252
|
+
// Filter out pending calls — they are not yet registered in Telecom
|
|
253
|
+
val currentCalls = allCalls.filter { (_, call) -> !call.isPending }
|
|
254
|
+
|
|
255
|
+
// Detect new calls
|
|
256
|
+
for ((callId, call) in currentCalls) {
|
|
257
|
+
val previous = previousCalls[callId]
|
|
258
|
+
if (previous == null) {
|
|
259
|
+
// New call added
|
|
260
|
+
_listener?.onCallRegistered(callId, call.isIncoming())
|
|
261
|
+
} else {
|
|
262
|
+
// Existing call changed
|
|
263
|
+
if (previous.isMuted != call.isMuted) {
|
|
264
|
+
debugLog(TAG, "[repository] observeCalls: Mute changed for $callId: ${call.isMuted}")
|
|
265
|
+
_listener?.onMuteCallChanged(callId, call.isMuted)
|
|
214
266
|
}
|
|
215
|
-
if (previous.currentCallEndpoint !=
|
|
216
|
-
|
|
217
|
-
_listener?.onCallEndpointChanged(
|
|
267
|
+
if (previous.currentCallEndpoint != call.currentCallEndpoint) {
|
|
268
|
+
call.currentCallEndpoint?.let {
|
|
269
|
+
_listener?.onCallEndpointChanged(callId, it.name.toString())
|
|
218
270
|
}
|
|
219
271
|
}
|
|
220
272
|
}
|
|
273
|
+
_listener?.onCallStateChanged(callId, call)
|
|
221
274
|
}
|
|
222
|
-
|
|
275
|
+
|
|
276
|
+
// Detect removed calls
|
|
277
|
+
for ((callId, _) in previousCalls) {
|
|
278
|
+
if (!currentCalls.containsKey(callId)) {
|
|
279
|
+
_listener?.onCallStateChanged(callId, Call.None)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
previousCalls = currentCalls
|
|
223
284
|
}
|
|
224
285
|
.launchIn(scope)
|
|
225
286
|
}
|
|
226
287
|
|
|
227
288
|
/** Collect the action source to handle client actions inside the call scope */
|
|
228
|
-
private suspend fun CallControlScope.processCallActions(
|
|
289
|
+
private suspend fun CallControlScope.processCallActions(
|
|
290
|
+
callId: String,
|
|
291
|
+
flags: CallActionFlags,
|
|
292
|
+
actionSource: Flow<CallAction>
|
|
293
|
+
) {
|
|
229
294
|
actionSource.collect { action ->
|
|
230
|
-
debugLog(TAG, "[repository] processCallActions: action: ${action::class.simpleName}")
|
|
295
|
+
debugLog(TAG, "[repository] processCallActions[$callId]: action: ${action::class.simpleName}")
|
|
231
296
|
when (action) {
|
|
232
297
|
is CallAction.Answer -> {
|
|
233
|
-
doAnswer(action.isAudioCall)
|
|
298
|
+
doAnswer(callId, flags, action.isAudioCall)
|
|
234
299
|
}
|
|
235
300
|
is CallAction.Disconnect -> {
|
|
236
|
-
doDisconnect(action)
|
|
301
|
+
doDisconnect(callId, flags, action)
|
|
237
302
|
}
|
|
238
303
|
is CallAction.SwitchAudioEndpoint -> {
|
|
239
|
-
doSwitchEndpoint(action)
|
|
304
|
+
doSwitchEndpoint(callId, action)
|
|
240
305
|
}
|
|
241
306
|
is CallAction.TransferCall -> {
|
|
242
307
|
debugLog(
|
|
243
308
|
TAG,
|
|
244
|
-
"[repository] processCallActions: Transfer to endpoint: ${action.endpointId}"
|
|
309
|
+
"[repository] processCallActions[$callId]: Transfer to endpoint: ${action.endpointId}"
|
|
245
310
|
)
|
|
246
|
-
val call =
|
|
311
|
+
val call = _calls.value[callId]
|
|
247
312
|
val endpoints =
|
|
248
313
|
call?.availableCallEndpoints?.firstOrNull {
|
|
249
314
|
it.identifier == action.endpointId
|
|
@@ -255,157 +320,141 @@ class TelecomCallRepository(context: Context) : CallRepository(context) {
|
|
|
255
320
|
} else {
|
|
256
321
|
Log.w(
|
|
257
322
|
TAG,
|
|
258
|
-
"[repository] processCallActions: Endpoint not found for transfer, ignoring"
|
|
323
|
+
"[repository] processCallActions[$callId]: Endpoint not found for transfer, ignoring"
|
|
259
324
|
)
|
|
260
325
|
}
|
|
261
326
|
}
|
|
262
327
|
CallAction.Hold -> {
|
|
263
328
|
when (val result = setInactive()) {
|
|
264
329
|
is CallControlResult.Success -> {
|
|
265
|
-
onIsCallInactive()
|
|
330
|
+
onIsCallInactive(callId)()
|
|
266
331
|
}
|
|
267
332
|
is CallControlResult.Error -> {
|
|
268
333
|
Log.e(
|
|
269
334
|
TAG,
|
|
270
|
-
"[repository] processCallActions: Hold action failed with error code: ${result.errorCode}"
|
|
335
|
+
"[repository] processCallActions[$callId]: Hold action failed with error code: ${result.errorCode}"
|
|
271
336
|
)
|
|
272
|
-
|
|
337
|
+
updateCallById(callId) { copy(errorCode = result.errorCode) }
|
|
273
338
|
}
|
|
274
339
|
}
|
|
275
340
|
}
|
|
276
341
|
CallAction.Activate -> {
|
|
277
342
|
when (val result = setActive()) {
|
|
278
343
|
is CallControlResult.Success -> {
|
|
279
|
-
onIsCallActive()
|
|
344
|
+
onIsCallActive(callId)()
|
|
280
345
|
}
|
|
281
346
|
is CallControlResult.Error -> {
|
|
282
347
|
Log.e(
|
|
283
348
|
TAG,
|
|
284
|
-
"[repository] processCallActions: Activate action failed with error code: ${result.errorCode}"
|
|
349
|
+
"[repository] processCallActions[$callId]: Activate action failed with error code: ${result.errorCode}"
|
|
285
350
|
)
|
|
286
|
-
|
|
351
|
+
updateCallById(callId) { copy(errorCode = result.errorCode) }
|
|
287
352
|
}
|
|
288
353
|
}
|
|
289
354
|
}
|
|
290
355
|
is CallAction.ToggleMute -> {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
updateCurrentCall {
|
|
295
|
-
val newMutedState = action.isMute
|
|
296
|
-
copy(isMuted = newMutedState)
|
|
356
|
+
debugLog(TAG, "[repository] processCallActions[$callId]: Toggling mute: ${action.isMute}")
|
|
357
|
+
updateCallById(callId) {
|
|
358
|
+
copy(isMuted = action.isMute)
|
|
297
359
|
}
|
|
298
360
|
}
|
|
299
361
|
}
|
|
300
362
|
}
|
|
301
|
-
debugLog(TAG, "[repository] processCallActions: Action collection ended")
|
|
363
|
+
debugLog(TAG, "[repository] processCallActions[$callId]: Action collection ended")
|
|
302
364
|
}
|
|
303
365
|
|
|
304
366
|
|
|
305
|
-
private suspend fun CallControlScope.doSwitchEndpoint(action: CallAction.SwitchAudioEndpoint) {
|
|
306
|
-
debugLog(TAG, "[repository] doSwitchEndpoint: Switching to endpoint: ${action.endpointId}")
|
|
307
|
-
|
|
308
|
-
|
|
367
|
+
private suspend fun CallControlScope.doSwitchEndpoint(callId: String, action: CallAction.SwitchAudioEndpoint) {
|
|
368
|
+
debugLog(TAG, "[repository] doSwitchEndpoint[$callId]: Switching to endpoint: ${action.endpointId}")
|
|
369
|
+
val call = _calls.value[callId]
|
|
370
|
+
if (call == null) {
|
|
371
|
+
Log.w(TAG, "[repository] doSwitchEndpoint[$callId]: Call not found, ignoring")
|
|
309
372
|
return
|
|
310
373
|
}
|
|
311
|
-
|
|
312
|
-
val endpoints = (_currentCall.value as Call.Registered).availableCallEndpoints
|
|
313
|
-
// Switch to the given endpoint or fallback to the best possible one.
|
|
374
|
+
val endpoints = call.availableCallEndpoints
|
|
314
375
|
val newEndpoint = endpoints.firstOrNull { it.identifier == action.endpointId }
|
|
315
376
|
|
|
316
377
|
if (newEndpoint != null) {
|
|
317
378
|
debugLog(
|
|
318
379
|
TAG,
|
|
319
|
-
"[repository] doSwitchEndpoint: Found endpoint: ${newEndpoint.name}, requesting change"
|
|
380
|
+
"[repository] doSwitchEndpoint[$callId]: Found endpoint: ${newEndpoint.name}, requesting change"
|
|
320
381
|
)
|
|
321
382
|
requestEndpointChange(newEndpoint).also {
|
|
322
|
-
debugLog(TAG, "[repository] doSwitchEndpoint: Endpoint change result: $it")
|
|
383
|
+
debugLog(TAG, "[repository] doSwitchEndpoint[$callId]: Endpoint change result: $it")
|
|
323
384
|
}
|
|
324
385
|
} else {
|
|
325
|
-
Log.w(TAG, "[repository] doSwitchEndpoint: Endpoint not found in available endpoints")
|
|
386
|
+
Log.w(TAG, "[repository] doSwitchEndpoint[$callId]: Endpoint not found in available endpoints")
|
|
326
387
|
}
|
|
327
388
|
}
|
|
328
389
|
|
|
329
|
-
private suspend fun CallControlScope.doDisconnect(action: CallAction.Disconnect) {
|
|
330
|
-
isSelfDisconnected
|
|
331
|
-
debugLog(TAG, "[repository] doDisconnect: Disconnecting call with cause: ${action.cause}")
|
|
390
|
+
private suspend fun CallControlScope.doDisconnect(callId: String, flags: CallActionFlags, action: CallAction.Disconnect) {
|
|
391
|
+
flags.isSelfDisconnected.set(true)
|
|
392
|
+
debugLog(TAG, "[repository] doDisconnect[$callId]: Disconnecting call with cause: ${action.cause}")
|
|
332
393
|
disconnect(action.cause)
|
|
333
|
-
debugLog(TAG, "[repository] doDisconnect: Disconnect called, triggering onIsCallDisconnected")
|
|
334
|
-
onIsCallDisconnected(action.cause)
|
|
394
|
+
debugLog(TAG, "[repository] doDisconnect[$callId]: Disconnect called, triggering onIsCallDisconnected")
|
|
395
|
+
onIsCallDisconnected(callId, flags)(action.cause)
|
|
335
396
|
}
|
|
336
397
|
|
|
337
|
-
private suspend fun CallControlScope.doAnswer(isAudioCall: Boolean) {
|
|
338
|
-
isSelfAnswered
|
|
398
|
+
private suspend fun CallControlScope.doAnswer(callId: String, flags: CallActionFlags, isAudioCall: Boolean) {
|
|
399
|
+
flags.isSelfAnswered.set(true)
|
|
339
400
|
val callType =
|
|
340
401
|
if (isAudioCall) CallAttributesCompat.CALL_TYPE_AUDIO_CALL
|
|
341
402
|
else CallAttributesCompat.CALL_TYPE_VIDEO_CALL
|
|
342
403
|
|
|
343
404
|
when (val result = answer(callType)) {
|
|
344
405
|
is CallControlResult.Success -> {
|
|
345
|
-
onIsCallAnswered(callType)
|
|
406
|
+
onIsCallAnswered(callId, flags)(callType)
|
|
346
407
|
}
|
|
347
408
|
is CallControlResult.Error -> {
|
|
348
409
|
Log.e(
|
|
349
410
|
TAG,
|
|
350
|
-
"[repository] doAnswer: Answer failed with error code: ${result.errorCode}"
|
|
411
|
+
"[repository] doAnswer[$callId]: Answer failed with error code: ${result.errorCode}"
|
|
351
412
|
)
|
|
352
|
-
isSelfAnswered
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
413
|
+
flags.isSelfAnswered.set(false)
|
|
414
|
+
val call = _calls.value[callId]
|
|
415
|
+
if (call != null) {
|
|
416
|
+
removeCall(callId)
|
|
417
|
+
_listener?.onIsCallDisconnected(
|
|
418
|
+
callId,
|
|
419
|
+
DisconnectCause(DisconnectCause.BUSY),
|
|
420
|
+
EventSource.APP
|
|
358
421
|
)
|
|
359
422
|
}
|
|
360
423
|
}
|
|
361
424
|
}
|
|
362
425
|
}
|
|
363
426
|
|
|
364
|
-
|
|
365
|
-
* Can the call be successfully answered?? TIP: We would check the connection/call state to see
|
|
366
|
-
* if we can answer a call Example you may need to wait for another call to hold.
|
|
367
|
-
*/
|
|
368
|
-
val onIsCallAnswered: suspend (type: Int) -> Unit = {
|
|
427
|
+
private fun onIsCallAnswered(callId: String, flags: CallActionFlags): suspend (type: Int) -> Unit = {
|
|
369
428
|
debugLog(
|
|
370
429
|
TAG,
|
|
371
|
-
"[repository] onIsCallAnswered: Call answered, type: $it, isSelfAnswered: $isSelfAnswered"
|
|
430
|
+
"[repository] onIsCallAnswered[$callId]: Call answered, type: $it, isSelfAnswered: ${flags.isSelfAnswered.get()}"
|
|
372
431
|
)
|
|
373
|
-
|
|
432
|
+
updateCallById(callId) { copy(isActive = true, isOnHold = false) }
|
|
374
433
|
|
|
375
|
-
val
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
_listener?.onIsCallAnswered(call.id, source)
|
|
434
|
+
val source = if (flags.isSelfAnswered.get()) EventSource.APP else EventSource.SYS
|
|
435
|
+
if (_calls.value.containsKey(callId)) {
|
|
436
|
+
_listener?.onIsCallAnswered(callId, source)
|
|
379
437
|
}
|
|
380
|
-
isSelfAnswered
|
|
381
|
-
debugLog(TAG, "[repository] onIsCallAnswered: Call state updated to active")
|
|
438
|
+
flags.isSelfAnswered.set(false)
|
|
439
|
+
debugLog(TAG, "[repository] onIsCallAnswered[$callId]: Call state updated to active")
|
|
382
440
|
}
|
|
383
441
|
|
|
384
|
-
|
|
385
|
-
val onIsCallDisconnected: suspend (cause: DisconnectCause) -> Unit = {
|
|
442
|
+
private fun onIsCallDisconnected(callId: String, flags: CallActionFlags): suspend (cause: DisconnectCause) -> Unit = { cause ->
|
|
386
443
|
debugLog(
|
|
387
444
|
TAG,
|
|
388
|
-
"[repository] onIsCallDisconnected: Call disconnected, cause: ${
|
|
445
|
+
"[repository] onIsCallDisconnected[$callId]: Call disconnected, cause: ${cause.reason}, description: ${cause.description}"
|
|
389
446
|
)
|
|
390
|
-
val source = if (isSelfDisconnected) EventSource.APP else EventSource.SYS
|
|
391
|
-
var callId: String? = null
|
|
392
|
-
if (_currentCall.value is Call.Registered) {
|
|
393
|
-
callId = (_currentCall.value as Call.Registered).id
|
|
394
|
-
}
|
|
447
|
+
val source = if (flags.isSelfDisconnected.get()) EventSource.APP else EventSource.SYS
|
|
395
448
|
|
|
396
|
-
|
|
397
|
-
_listener?.onIsCallDisconnected(callId,
|
|
398
|
-
isSelfDisconnected
|
|
399
|
-
debugLog(TAG, "[repository] onIsCallDisconnected: Call
|
|
449
|
+
removeCall(callId)
|
|
450
|
+
_listener?.onIsCallDisconnected(callId, cause, source)
|
|
451
|
+
flags.isSelfDisconnected.set(false)
|
|
452
|
+
debugLog(TAG, "[repository] onIsCallDisconnected[$callId]: Call removed from map")
|
|
400
453
|
}
|
|
401
454
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
*/
|
|
406
|
-
val onIsCallActive: suspend () -> Unit = {
|
|
407
|
-
debugLog(TAG, "[repository] onIsCallActive: Call became active")
|
|
408
|
-
updateCurrentCall {
|
|
455
|
+
private fun onIsCallActive(callId: String): suspend () -> Unit = {
|
|
456
|
+
debugLog(TAG, "[repository] onIsCallActive[$callId]: Call became active")
|
|
457
|
+
updateCallById(callId) {
|
|
409
458
|
copy(
|
|
410
459
|
errorCode = null,
|
|
411
460
|
isActive = true,
|
|
@@ -413,23 +462,20 @@ class TelecomCallRepository(context: Context) : CallRepository(context) {
|
|
|
413
462
|
)
|
|
414
463
|
}
|
|
415
464
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
_listener?.onIsCallActive(call.id)
|
|
465
|
+
if (_calls.value.containsKey(callId)) {
|
|
466
|
+
_listener?.onIsCallActive(callId)
|
|
419
467
|
}
|
|
420
|
-
debugLog(TAG, "[repository] onIsCallActive: Call state updated")
|
|
468
|
+
debugLog(TAG, "[repository] onIsCallActive[$callId]: Call state updated")
|
|
421
469
|
}
|
|
422
470
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
updateCurrentCall { copy(errorCode = null, isOnHold = true) }
|
|
471
|
+
private fun onIsCallInactive(callId: String): suspend () -> Unit = {
|
|
472
|
+
debugLog(TAG, "[repository] onIsCallInactive[$callId]: Call became inactive (on hold)")
|
|
473
|
+
updateCallById(callId) { copy(errorCode = null, isOnHold = true) }
|
|
427
474
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
_listener?.onIsCallInactive(call.id)
|
|
475
|
+
if (_calls.value.containsKey(callId)) {
|
|
476
|
+
_listener?.onIsCallInactive(callId)
|
|
431
477
|
}
|
|
432
|
-
debugLog(TAG, "[repository] onIsCallInactive: Call state updated to on hold")
|
|
478
|
+
debugLog(TAG, "[repository] onIsCallInactive[$callId]: Call state updated to on hold")
|
|
433
479
|
}
|
|
434
480
|
|
|
435
481
|
}
|