@qusaieilouti99/call-manager 0.1.51 → 0.1.52
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/src/main/java/com/margelo/nitro/qusaieilouti99/callmanager/CallEngine.kt +207 -21
- package/android/src/main/java/com/margelo/nitro/qusaieilouti99/callmanager/MyConnection.kt +27 -1
- package/lib/typescript/src/CallEventType.d.ts +1 -1
- package/lib/typescript/src/CallEventType.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JCallEventType.hpp +3 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/qusaieilouti99/callmanager/CallEventType.kt +1 -0
- package/nitrogen/generated/ios/swift/CallEventType.swift +4 -0
- package/nitrogen/generated/shared/c++/CallEventType.hpp +10 -6
- package/package.json +1 -1
- package/src/CallEventType.ts +1 -0
|
@@ -13,6 +13,7 @@ import android.graphics.Color
|
|
|
13
13
|
import android.media.AudioAttributes
|
|
14
14
|
import android.media.AudioDeviceCallback
|
|
15
15
|
import android.media.AudioDeviceInfo
|
|
16
|
+
import android.media.AudioFocusRequest
|
|
16
17
|
import android.media.AudioManager
|
|
17
18
|
import android.media.MediaPlayer
|
|
18
19
|
import android.media.RingtoneManager
|
|
@@ -53,6 +54,7 @@ object CallEngine {
|
|
|
53
54
|
private var audioManager: AudioManager? = null
|
|
54
55
|
private var wakeLock: PowerManager.WakeLock? = null
|
|
55
56
|
private var appContext: Context? = null
|
|
57
|
+
private var audioFocusRequest: AudioFocusRequest? = null
|
|
56
58
|
|
|
57
59
|
// Call State Management
|
|
58
60
|
private val activeCalls = ConcurrentHashMap<String, CallInfo>()
|
|
@@ -63,6 +65,11 @@ object CallEngine {
|
|
|
63
65
|
// Audio State Tracking
|
|
64
66
|
private var lastAudioRoutesInfo: AudioRoutesInfo? = null
|
|
65
67
|
private var lastMuteState: Boolean = false
|
|
68
|
+
private var hasAudioFocus: Boolean = false
|
|
69
|
+
|
|
70
|
+
// System Call State Tracking
|
|
71
|
+
private var isSystemCallActive: Boolean = false
|
|
72
|
+
private var wasHeldBySystem: Boolean = false
|
|
66
73
|
|
|
67
74
|
// Lock Screen Bypass
|
|
68
75
|
private var lockScreenBypassActive = false
|
|
@@ -80,19 +87,147 @@ object CallEngine {
|
|
|
80
87
|
val callData: String,
|
|
81
88
|
var state: CallState,
|
|
82
89
|
val callType: String = "Audio",
|
|
83
|
-
val timestamp: Long = System.currentTimeMillis()
|
|
90
|
+
val timestamp: Long = System.currentTimeMillis(),
|
|
91
|
+
var wasHeldBySystem: Boolean = false
|
|
84
92
|
)
|
|
85
93
|
|
|
86
94
|
enum class CallState {
|
|
87
|
-
INCOMING, DIALING, ACTIVE, HELD, ENDED
|
|
95
|
+
INCOMING, DIALING, ACTIVE, HELD, HELD_BY_SYSTEM, ENDED
|
|
88
96
|
}
|
|
89
97
|
|
|
90
98
|
interface LockScreenBypassCallback {
|
|
91
99
|
fun onLockScreenBypassChanged(shouldBypass: Boolean)
|
|
92
100
|
}
|
|
93
101
|
|
|
94
|
-
|
|
95
|
-
|
|
102
|
+
// --- Audio Focus Management ---
|
|
103
|
+
private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
|
|
104
|
+
Log.d(TAG, "Audio focus changed: $focusChange")
|
|
105
|
+
when (focusChange) {
|
|
106
|
+
AudioManager.AUDIOFOCUS_LOSS -> {
|
|
107
|
+
// Lost focus permanently - likely due to phone call
|
|
108
|
+
handleAudioFocusLoss()
|
|
109
|
+
}
|
|
110
|
+
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
|
|
111
|
+
// Lost focus temporarily
|
|
112
|
+
handleAudioFocusLossTransient()
|
|
113
|
+
}
|
|
114
|
+
AudioManager.AUDIOFOCUS_GAIN -> {
|
|
115
|
+
// Regained focus
|
|
116
|
+
handleAudioFocusGain()
|
|
117
|
+
}
|
|
118
|
+
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
|
|
119
|
+
// Can duck audio
|
|
120
|
+
handleAudioFocusLossCanDuck()
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private fun handleAudioFocusLoss() {
|
|
126
|
+
Log.d(TAG, "Audio focus lost permanently - likely system call active")
|
|
127
|
+
hasAudioFocus = false
|
|
128
|
+
isSystemCallActive = true
|
|
129
|
+
|
|
130
|
+
// Hold all active calls instead of ending them
|
|
131
|
+
activeCalls.values.filter { it.state == CallState.ACTIVE }.forEach { call ->
|
|
132
|
+
if (!call.wasHeldBySystem) {
|
|
133
|
+
call.wasHeldBySystem = true
|
|
134
|
+
call.state = CallState.HELD_BY_SYSTEM
|
|
135
|
+
|
|
136
|
+
val connection = telecomConnections[call.callId]
|
|
137
|
+
connection?.setOnHold()
|
|
138
|
+
|
|
139
|
+
emitEvent(CallEventType.CALL_HELD, JSONObject().apply {
|
|
140
|
+
put("callId", call.callId)
|
|
141
|
+
put("reason", "system_call_active")
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
notifySpecificCallStateChanged(appContext!!, call.callId, CallState.HELD_BY_SYSTEM)
|
|
145
|
+
Log.d(TAG, "Call ${call.callId} held by system due to audio focus loss")
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
stopRingback()
|
|
150
|
+
updateForegroundNotification(appContext!!)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private fun handleAudioFocusLossTransient() {
|
|
154
|
+
// Similar to permanent loss but may be temporary
|
|
155
|
+
handleAudioFocusLoss()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private fun handleAudioFocusGain() {
|
|
159
|
+
Log.d(TAG, "Audio focus regained - system call likely ended")
|
|
160
|
+
hasAudioFocus = true
|
|
161
|
+
isSystemCallActive = false
|
|
162
|
+
|
|
163
|
+
// Resume calls that were held by system
|
|
164
|
+
activeCalls.values.filter { it.state == CallState.HELD_BY_SYSTEM && it.wasHeldBySystem }.forEach { call ->
|
|
165
|
+
call.wasHeldBySystem = false
|
|
166
|
+
call.state = CallState.ACTIVE
|
|
167
|
+
|
|
168
|
+
val connection = telecomConnections[call.callId]
|
|
169
|
+
connection?.setActive()
|
|
170
|
+
|
|
171
|
+
emitEvent(CallEventType.CALL_UNHELD, JSONObject().apply {
|
|
172
|
+
put("callId", call.callId)
|
|
173
|
+
put("reason", "system_call_ended")
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
notifySpecificCallStateChanged(appContext!!, call.callId, CallState.ACTIVE)
|
|
177
|
+
Log.d(TAG, "Call ${call.callId} resumed after system call ended")
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
updateForegroundNotification(appContext!!)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private fun handleAudioFocusLossCanDuck() {
|
|
184
|
+
// Lower volume but continue
|
|
185
|
+
Log.d(TAG, "Audio focus loss - can duck")
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private fun requestAudioFocus(): Boolean {
|
|
189
|
+
audioManager = audioManager ?: appContext?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
|
|
190
|
+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
191
|
+
if (audioFocusRequest == null) {
|
|
192
|
+
audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
|
|
193
|
+
.setAudioAttributes(
|
|
194
|
+
AudioAttributes.Builder()
|
|
195
|
+
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
|
|
196
|
+
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
|
197
|
+
.build()
|
|
198
|
+
)
|
|
199
|
+
.setOnAudioFocusChangeListener(audioFocusChangeListener)
|
|
200
|
+
.build()
|
|
201
|
+
}
|
|
202
|
+
val result = audioManager?.requestAudioFocus(audioFocusRequest!!)
|
|
203
|
+
hasAudioFocus = result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
|
|
204
|
+
Log.d(TAG, "Audio focus request result: $result")
|
|
205
|
+
hasAudioFocus
|
|
206
|
+
} else {
|
|
207
|
+
@Suppress("DEPRECATION")
|
|
208
|
+
val result = audioManager?.requestAudioFocus(
|
|
209
|
+
audioFocusChangeListener,
|
|
210
|
+
AudioManager.STREAM_VOICE_CALL,
|
|
211
|
+
AudioManager.AUDIOFOCUS_GAIN
|
|
212
|
+
)
|
|
213
|
+
hasAudioFocus = result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
|
|
214
|
+
Log.d(TAG, "Audio focus request result (legacy): $result")
|
|
215
|
+
hasAudioFocus
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private fun abandonAudioFocus() {
|
|
220
|
+
audioManager = audioManager ?: appContext?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
|
|
221
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
222
|
+
audioFocusRequest?.let { request ->
|
|
223
|
+
audioManager?.abandonAudioFocusRequest(request)
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
@Suppress("DEPRECATION")
|
|
227
|
+
audioManager?.abandonAudioFocus(audioFocusChangeListener)
|
|
228
|
+
}
|
|
229
|
+
hasAudioFocus = false
|
|
230
|
+
Log.d(TAG, "Audio focus abandoned")
|
|
96
231
|
}
|
|
97
232
|
|
|
98
233
|
// --- Event System ---
|
|
@@ -176,6 +311,7 @@ object CallEngine {
|
|
|
176
311
|
obj.put("callData", it.callData)
|
|
177
312
|
obj.put("state", it.state.name)
|
|
178
313
|
obj.put("callType", it.callType)
|
|
314
|
+
obj.put("wasHeldBySystem", it.wasHeldBySystem)
|
|
179
315
|
jsonArray.put(obj)
|
|
180
316
|
}
|
|
181
317
|
val result = jsonArray.toString()
|
|
@@ -192,15 +328,13 @@ object CallEngine {
|
|
|
192
328
|
val incomingCall = activeCalls.values.find { it.state == CallState.INCOMING }
|
|
193
329
|
if (incomingCall != null && incomingCall.callId != callId) {
|
|
194
330
|
Log.d(TAG, "Incoming call collision detected. Auto-rejecting new call: $callId")
|
|
195
|
-
|
|
196
|
-
// Auto-reject the new call
|
|
197
331
|
rejectIncomingCallCollision(callId, "Another call is already incoming")
|
|
198
332
|
return
|
|
199
333
|
}
|
|
200
334
|
|
|
201
335
|
// Check if there's an active call when receiving incoming
|
|
202
|
-
val activeCall = activeCalls.values.find { it.state == CallState.ACTIVE }
|
|
203
|
-
if (activeCall != null) {
|
|
336
|
+
val activeCall = activeCalls.values.find { it.state == CallState.ACTIVE || it.state == CallState.HELD_BY_SYSTEM }
|
|
337
|
+
if (activeCall != null && !canMakeMultipleCalls) {
|
|
204
338
|
Log.d(TAG, "Active call exists when receiving incoming call. Auto-rejecting: $callId")
|
|
205
339
|
rejectIncomingCallCollision(callId, "Another call is already active")
|
|
206
340
|
return
|
|
@@ -212,7 +346,11 @@ object CallEngine {
|
|
|
212
346
|
|
|
213
347
|
if (!canMakeMultipleCalls && activeCalls.isNotEmpty()) {
|
|
214
348
|
Log.d(TAG, "Can't make multiple calls, holding existing calls.")
|
|
215
|
-
activeCalls.values.forEach {
|
|
349
|
+
activeCalls.values.forEach {
|
|
350
|
+
if (it.state == CallState.ACTIVE) {
|
|
351
|
+
it.state = CallState.HELD
|
|
352
|
+
}
|
|
353
|
+
}
|
|
216
354
|
}
|
|
217
355
|
|
|
218
356
|
activeCalls[callId] = CallInfo(callId, callData, CallState.INCOMING, parsedCallType)
|
|
@@ -266,7 +404,11 @@ object CallEngine {
|
|
|
266
404
|
|
|
267
405
|
if (!canMakeMultipleCalls && activeCalls.isNotEmpty()) {
|
|
268
406
|
Log.d(TAG, "Can't make multiple calls, holding existing calls before outgoing.")
|
|
269
|
-
activeCalls.values.forEach {
|
|
407
|
+
activeCalls.values.forEach {
|
|
408
|
+
if (it.state == CallState.ACTIVE) {
|
|
409
|
+
it.state = CallState.HELD
|
|
410
|
+
}
|
|
411
|
+
}
|
|
270
412
|
}
|
|
271
413
|
|
|
272
414
|
activeCalls[callId] = CallInfo(callId, callData, CallState.DIALING, parsedCallType)
|
|
@@ -290,10 +432,14 @@ object CallEngine {
|
|
|
290
432
|
startForegroundService(context)
|
|
291
433
|
Log.d(TAG, "Successfully reported outgoing call to TelecomManager via placeCall for $callId")
|
|
292
434
|
|
|
293
|
-
|
|
435
|
+
// Request audio focus for outgoing call
|
|
436
|
+
if (requestAudioFocus()) {
|
|
437
|
+
startRingback()
|
|
438
|
+
setInitialAudioRoute(context, parsedCallType)
|
|
439
|
+
}
|
|
440
|
+
|
|
294
441
|
bringAppToForeground(context)
|
|
295
442
|
keepScreenAwake(context, true)
|
|
296
|
-
setInitialAudioRoute(context, parsedCallType)
|
|
297
443
|
} catch (e: SecurityException) {
|
|
298
444
|
Log.e(TAG, "SecurityException: Failed to start outgoing call via placeCall. Check MANAGE_OWN_CALLS permission: ${e.message}", e)
|
|
299
445
|
endCall(context, callId)
|
|
@@ -331,12 +477,21 @@ object CallEngine {
|
|
|
331
477
|
stopRingback()
|
|
332
478
|
cancelIncomingCallUI(context)
|
|
333
479
|
|
|
480
|
+
// Request audio focus when answering
|
|
481
|
+
if (!hasAudioFocus) {
|
|
482
|
+
requestAudioFocus()
|
|
483
|
+
}
|
|
484
|
+
|
|
334
485
|
// Update call state
|
|
335
486
|
activeCalls[callId]?.state = CallState.ACTIVE
|
|
336
487
|
currentCallId = callId
|
|
337
488
|
|
|
338
489
|
if (!canMakeMultipleCalls) {
|
|
339
|
-
activeCalls.filter { it.key != callId }.values.forEach {
|
|
490
|
+
activeCalls.filter { it.key != callId }.values.forEach {
|
|
491
|
+
if (it.state == CallState.ACTIVE) {
|
|
492
|
+
it.state = CallState.HELD
|
|
493
|
+
}
|
|
494
|
+
}
|
|
340
495
|
}
|
|
341
496
|
|
|
342
497
|
// Bring app to foreground when call is answered
|
|
@@ -366,7 +521,7 @@ object CallEngine {
|
|
|
366
521
|
Log.d(TAG, "holdCall: $callId")
|
|
367
522
|
val callInfo = activeCalls[callId]
|
|
368
523
|
if (callInfo?.state != CallState.ACTIVE) {
|
|
369
|
-
Log.w(TAG, "Cannot hold call $callId - not in active state")
|
|
524
|
+
Log.w(TAG, "Cannot hold call $callId - not in active state (current: ${callInfo?.state})")
|
|
370
525
|
return
|
|
371
526
|
}
|
|
372
527
|
|
|
@@ -383,12 +538,35 @@ object CallEngine {
|
|
|
383
538
|
fun unholdCall(context: Context, callId: String) {
|
|
384
539
|
Log.d(TAG, "unholdCall: $callId")
|
|
385
540
|
val callInfo = activeCalls[callId]
|
|
386
|
-
if (callInfo?.state != CallState.HELD) {
|
|
387
|
-
Log.w(TAG, "Cannot unhold call $callId - not in held state")
|
|
541
|
+
if (callInfo?.state != CallState.HELD && callInfo?.state != CallState.HELD_BY_SYSTEM) {
|
|
542
|
+
Log.w(TAG, "Cannot unhold call $callId - not in held state (current: ${callInfo?.state})")
|
|
388
543
|
return
|
|
389
544
|
}
|
|
390
545
|
|
|
546
|
+
// If call was held by system, don't allow manual unhold until system allows it
|
|
547
|
+
if (callInfo.state == CallState.HELD_BY_SYSTEM && isSystemCallActive) {
|
|
548
|
+
Log.w(TAG, "Cannot unhold call $callId - held by system and system call still active")
|
|
549
|
+
emitEvent(CallEventType.CALL_UNHOLD_FAILED, JSONObject().apply {
|
|
550
|
+
put("callId", callId)
|
|
551
|
+
put("reason", "system_call_active")
|
|
552
|
+
})
|
|
553
|
+
return
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Request audio focus before unholding
|
|
557
|
+
if (!hasAudioFocus) {
|
|
558
|
+
if (!requestAudioFocus()) {
|
|
559
|
+
Log.w(TAG, "Cannot unhold call $callId - failed to gain audio focus")
|
|
560
|
+
emitEvent(CallEventType.CALL_UNHOLD_FAILED, JSONObject().apply {
|
|
561
|
+
put("callId", callId)
|
|
562
|
+
put("reason", "audio_focus_failed")
|
|
563
|
+
})
|
|
564
|
+
return
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
391
568
|
activeCalls[callId]?.state = CallState.ACTIVE
|
|
569
|
+
activeCalls[callId]?.wasHeldBySystem = false
|
|
392
570
|
val connection = telecomConnections[callId]
|
|
393
571
|
connection?.setActive()
|
|
394
572
|
|
|
@@ -622,6 +800,7 @@ object CallEngine {
|
|
|
622
800
|
audioManager?.stopBluetoothSco()
|
|
623
801
|
audioManager?.isBluetoothScoOn = false
|
|
624
802
|
audioManager?.isSpeakerphoneOn = false
|
|
803
|
+
abandonAudioFocus()
|
|
625
804
|
} else {
|
|
626
805
|
Log.d(TAG, "Audio mode not reset; ${activeCalls.size} calls still active.")
|
|
627
806
|
}
|
|
@@ -700,7 +879,9 @@ object CallEngine {
|
|
|
700
879
|
fun isCallActive(): Boolean = activeCalls.any {
|
|
701
880
|
it.value.state == CallState.ACTIVE ||
|
|
702
881
|
it.value.state == CallState.INCOMING ||
|
|
703
|
-
it.value.state == CallState.DIALING
|
|
882
|
+
it.value.state == CallState.DIALING ||
|
|
883
|
+
it.value.state == CallState.HELD ||
|
|
884
|
+
it.value.state == CallState.HELD_BY_SYSTEM
|
|
704
885
|
}
|
|
705
886
|
|
|
706
887
|
private fun validateOutgoingCallRequest(): Boolean {
|
|
@@ -730,8 +911,6 @@ object CallEngine {
|
|
|
730
911
|
CoroutineScope(Dispatchers.IO).launch {
|
|
731
912
|
try {
|
|
732
913
|
// TODO: Add your server HTTP request here
|
|
733
|
-
// Example:
|
|
734
|
-
// ApiService.rejectCall(callId, reason)
|
|
735
914
|
Log.d(TAG, "Server rejection request would be made here for callId: $callId, reason: $reason")
|
|
736
915
|
} catch (e: Exception) {
|
|
737
916
|
Log.e(TAG, "Failed to send rejection to server", e)
|
|
@@ -857,7 +1036,9 @@ object CallEngine {
|
|
|
857
1036
|
|
|
858
1037
|
// Find the current active call to pass its info
|
|
859
1038
|
val currentCall = activeCalls.values.find {
|
|
860
|
-
it.state == CallState.ACTIVE || it.state == CallState.INCOMING ||
|
|
1039
|
+
it.state == CallState.ACTIVE || it.state == CallState.INCOMING ||
|
|
1040
|
+
it.state == CallState.DIALING || it.state == CallState.HELD ||
|
|
1041
|
+
it.state == CallState.HELD_BY_SYSTEM
|
|
861
1042
|
}
|
|
862
1043
|
|
|
863
1044
|
val intent = Intent(context, CallForegroundService::class.java)
|
|
@@ -1022,6 +1203,7 @@ object CallEngine {
|
|
|
1022
1203
|
put("callData", callInfo.callData)
|
|
1023
1204
|
put("state", newState.name)
|
|
1024
1205
|
put("callType", callInfo.callType)
|
|
1206
|
+
put("wasHeldBySystem", callInfo.wasHeldBySystem)
|
|
1025
1207
|
}
|
|
1026
1208
|
|
|
1027
1209
|
Log.d(TAG, "Specific call state changed. Emitting CALL_STATE_CHANGED for $callId: $newState")
|
|
@@ -1031,8 +1213,9 @@ object CallEngine {
|
|
|
1031
1213
|
private fun updateForegroundNotification(context: Context) {
|
|
1032
1214
|
val activeCall = activeCalls.values.find { it.state == CallState.ACTIVE }
|
|
1033
1215
|
val heldCall = activeCalls.values.find { it.state == CallState.HELD }
|
|
1216
|
+
val heldBySystemCall = activeCalls.values.find { it.state == CallState.HELD_BY_SYSTEM }
|
|
1034
1217
|
|
|
1035
|
-
val callToShow = activeCall ?: heldCall
|
|
1218
|
+
val callToShow = activeCall ?: heldCall ?: heldBySystemCall
|
|
1036
1219
|
callToShow?.let {
|
|
1037
1220
|
val intent = Intent(context, CallForegroundService::class.java)
|
|
1038
1221
|
intent.putExtra("UPDATE_NOTIFICATION", true)
|
|
@@ -1053,5 +1236,8 @@ object CallEngine {
|
|
|
1053
1236
|
stopForegroundService(context)
|
|
1054
1237
|
keepScreenAwake(context, false)
|
|
1055
1238
|
resetAudioMode(context)
|
|
1239
|
+
abandonAudioFocus()
|
|
1240
|
+
isSystemCallActive = false
|
|
1241
|
+
wasHeldBySystem = false
|
|
1056
1242
|
}
|
|
1057
1243
|
}
|
|
@@ -33,7 +33,9 @@ class MyConnection(
|
|
|
33
33
|
} catch (e: Exception) { "Audio" }
|
|
34
34
|
|
|
35
35
|
connectionProperties = Connection.PROPERTY_SELF_MANAGED
|
|
36
|
-
connectionCapabilities = Connection.CAPABILITY_SUPPORT_HOLD or
|
|
36
|
+
connectionCapabilities = Connection.CAPABILITY_SUPPORT_HOLD or
|
|
37
|
+
Connection.CAPABILITY_MUTE or
|
|
38
|
+
Connection.CAPABILITY_HOLD
|
|
37
39
|
|
|
38
40
|
if (currentCallType == "Video") {
|
|
39
41
|
Log.d(TAG, "MyConnection for callId $callId initialized as VIDEO call.")
|
|
@@ -70,12 +72,18 @@ class MyConnection(
|
|
|
70
72
|
override fun onHold() {
|
|
71
73
|
super.onHold()
|
|
72
74
|
Log.d(TAG, "Call held via Telecom for callId: $callId")
|
|
75
|
+
|
|
76
|
+
// This is called by the system when it wants to hold our call
|
|
77
|
+
// Usually happens when a phone call comes in
|
|
73
78
|
CallEngine.holdCall(context, callId)
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
override fun onUnhold() {
|
|
77
82
|
super.onUnhold()
|
|
78
83
|
Log.d(TAG, "Call unheld via Telecom for callId: $callId")
|
|
84
|
+
|
|
85
|
+
// This is called by the system when it's safe to resume our call
|
|
86
|
+
// Usually happens when a phone call ends
|
|
79
87
|
CallEngine.unholdCall(context, callId)
|
|
80
88
|
}
|
|
81
89
|
|
|
@@ -130,4 +138,22 @@ class MyConnection(
|
|
|
130
138
|
Log.d(TAG, "onShowIncomingCallUi for callId: $callId")
|
|
131
139
|
// Don't bring app to foreground for incoming calls automatically
|
|
132
140
|
}
|
|
141
|
+
|
|
142
|
+
override fun onStateChanged(state: Int) {
|
|
143
|
+
super.onStateChanged(state)
|
|
144
|
+
Log.d(TAG, "Connection state changed for callId: $callId. New state: $state")
|
|
145
|
+
|
|
146
|
+
when (state) {
|
|
147
|
+
STATE_HOLDING -> {
|
|
148
|
+
Log.d(TAG, "Connection is now holding for callId: $callId")
|
|
149
|
+
}
|
|
150
|
+
STATE_ACTIVE -> {
|
|
151
|
+
Log.d(TAG, "Connection is now active for callId: $callId")
|
|
152
|
+
}
|
|
153
|
+
STATE_DISCONNECTED -> {
|
|
154
|
+
Log.d(TAG, "Connection is now disconnected for callId: $callId")
|
|
155
|
+
CallEngine.removeTelecomConnection(callId)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
133
159
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type CallEventType = 'INITIAL_CALL_STATE' | 'CALL_STATE_CHANGED' | 'AUDIO_DEVICES_CHANGED' | 'AUDIO_ROUTE_CHANGED' | 'CALL_HELD' | 'CALL_UNHELD' | 'CALL_MUTED' | 'CALL_UNMUTED' | 'CALL_ANSWERED' | 'CALL_REJECTED' | 'CALL_ENDED' | 'DTMF_TONE';
|
|
1
|
+
export type CallEventType = 'INITIAL_CALL_STATE' | 'CALL_STATE_CHANGED' | 'AUDIO_DEVICES_CHANGED' | 'AUDIO_ROUTE_CHANGED' | 'CALL_HELD' | 'CALL_UNHELD' | 'CALL_UNHOLD_FAILED' | 'CALL_MUTED' | 'CALL_UNMUTED' | 'CALL_ANSWERED' | 'CALL_REJECTED' | 'CALL_ENDED' | 'DTMF_TONE';
|
|
2
2
|
//# sourceMappingURL=CallEventType.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CallEventType.d.ts","sourceRoot":"","sources":["../../../src/CallEventType.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,aAAa,GACrB,oBAAoB,GACpB,oBAAoB,GACpB,uBAAuB,GACvB,qBAAqB,GACrB,WAAW,GACX,aAAa,GACb,YAAY,GACZ,cAAc,GACd,eAAe,GACf,eAAe,GACf,YAAY,GACZ,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"CallEventType.d.ts","sourceRoot":"","sources":["../../../src/CallEventType.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,aAAa,GACrB,oBAAoB,GACpB,oBAAoB,GACpB,uBAAuB,GACvB,qBAAqB,GACrB,WAAW,GACX,aAAa,GACb,oBAAoB,GACpB,YAAY,GACZ,cAAc,GACd,eAAe,GACf,eAAe,GACf,YAAY,GACZ,WAAW,CAAC"}
|
|
@@ -47,6 +47,7 @@ namespace margelo::nitro::qusaieilouti99_callmanager {
|
|
|
47
47
|
static const auto fieldAUDIO_ROUTE_CHANGED = clazz->getStaticField<JCallEventType>("AUDIO_ROUTE_CHANGED");
|
|
48
48
|
static const auto fieldCALL_HELD = clazz->getStaticField<JCallEventType>("CALL_HELD");
|
|
49
49
|
static const auto fieldCALL_UNHELD = clazz->getStaticField<JCallEventType>("CALL_UNHELD");
|
|
50
|
+
static const auto fieldCALL_UNHOLD_FAILED = clazz->getStaticField<JCallEventType>("CALL_UNHOLD_FAILED");
|
|
50
51
|
static const auto fieldCALL_MUTED = clazz->getStaticField<JCallEventType>("CALL_MUTED");
|
|
51
52
|
static const auto fieldCALL_UNMUTED = clazz->getStaticField<JCallEventType>("CALL_UNMUTED");
|
|
52
53
|
static const auto fieldCALL_ANSWERED = clazz->getStaticField<JCallEventType>("CALL_ANSWERED");
|
|
@@ -67,6 +68,8 @@ namespace margelo::nitro::qusaieilouti99_callmanager {
|
|
|
67
68
|
return clazz->getStaticFieldValue(fieldCALL_HELD);
|
|
68
69
|
case CallEventType::CALL_UNHELD:
|
|
69
70
|
return clazz->getStaticFieldValue(fieldCALL_UNHELD);
|
|
71
|
+
case CallEventType::CALL_UNHOLD_FAILED:
|
|
72
|
+
return clazz->getStaticFieldValue(fieldCALL_UNHOLD_FAILED);
|
|
70
73
|
case CallEventType::CALL_MUTED:
|
|
71
74
|
return clazz->getStaticFieldValue(fieldCALL_MUTED);
|
|
72
75
|
case CallEventType::CALL_UNMUTED:
|
|
@@ -29,6 +29,8 @@ public extension CallEventType {
|
|
|
29
29
|
self = .callHeld
|
|
30
30
|
case "CALL_UNHELD":
|
|
31
31
|
self = .callUnheld
|
|
32
|
+
case "CALL_UNHOLD_FAILED":
|
|
33
|
+
self = .callUnholdFailed
|
|
32
34
|
case "CALL_MUTED":
|
|
33
35
|
self = .callMuted
|
|
34
36
|
case "CALL_UNMUTED":
|
|
@@ -63,6 +65,8 @@ public extension CallEventType {
|
|
|
63
65
|
return "CALL_HELD"
|
|
64
66
|
case .callUnheld:
|
|
65
67
|
return "CALL_UNHELD"
|
|
68
|
+
case .callUnholdFailed:
|
|
69
|
+
return "CALL_UNHOLD_FAILED"
|
|
66
70
|
case .callMuted:
|
|
67
71
|
return "CALL_MUTED"
|
|
68
72
|
case .callUnmuted:
|
|
@@ -35,12 +35,13 @@ namespace margelo::nitro::qusaieilouti99_callmanager {
|
|
|
35
35
|
AUDIO_ROUTE_CHANGED SWIFT_NAME(audioRouteChanged) = 3,
|
|
36
36
|
CALL_HELD SWIFT_NAME(callHeld) = 4,
|
|
37
37
|
CALL_UNHELD SWIFT_NAME(callUnheld) = 5,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
CALL_UNHOLD_FAILED SWIFT_NAME(callUnholdFailed) = 6,
|
|
39
|
+
CALL_MUTED SWIFT_NAME(callMuted) = 7,
|
|
40
|
+
CALL_UNMUTED SWIFT_NAME(callUnmuted) = 8,
|
|
41
|
+
CALL_ANSWERED SWIFT_NAME(callAnswered) = 9,
|
|
42
|
+
CALL_REJECTED SWIFT_NAME(callRejected) = 10,
|
|
43
|
+
CALL_ENDED SWIFT_NAME(callEnded) = 11,
|
|
44
|
+
DTMF_TONE SWIFT_NAME(dtmfTone) = 12,
|
|
44
45
|
} CLOSED_ENUM;
|
|
45
46
|
|
|
46
47
|
} // namespace margelo::nitro::qusaieilouti99_callmanager
|
|
@@ -61,6 +62,7 @@ namespace margelo::nitro {
|
|
|
61
62
|
case hashString("AUDIO_ROUTE_CHANGED"): return CallEventType::AUDIO_ROUTE_CHANGED;
|
|
62
63
|
case hashString("CALL_HELD"): return CallEventType::CALL_HELD;
|
|
63
64
|
case hashString("CALL_UNHELD"): return CallEventType::CALL_UNHELD;
|
|
65
|
+
case hashString("CALL_UNHOLD_FAILED"): return CallEventType::CALL_UNHOLD_FAILED;
|
|
64
66
|
case hashString("CALL_MUTED"): return CallEventType::CALL_MUTED;
|
|
65
67
|
case hashString("CALL_UNMUTED"): return CallEventType::CALL_UNMUTED;
|
|
66
68
|
case hashString("CALL_ANSWERED"): return CallEventType::CALL_ANSWERED;
|
|
@@ -79,6 +81,7 @@ namespace margelo::nitro {
|
|
|
79
81
|
case CallEventType::AUDIO_ROUTE_CHANGED: return JSIConverter<std::string>::toJSI(runtime, "AUDIO_ROUTE_CHANGED");
|
|
80
82
|
case CallEventType::CALL_HELD: return JSIConverter<std::string>::toJSI(runtime, "CALL_HELD");
|
|
81
83
|
case CallEventType::CALL_UNHELD: return JSIConverter<std::string>::toJSI(runtime, "CALL_UNHELD");
|
|
84
|
+
case CallEventType::CALL_UNHOLD_FAILED: return JSIConverter<std::string>::toJSI(runtime, "CALL_UNHOLD_FAILED");
|
|
82
85
|
case CallEventType::CALL_MUTED: return JSIConverter<std::string>::toJSI(runtime, "CALL_MUTED");
|
|
83
86
|
case CallEventType::CALL_UNMUTED: return JSIConverter<std::string>::toJSI(runtime, "CALL_UNMUTED");
|
|
84
87
|
case CallEventType::CALL_ANSWERED: return JSIConverter<std::string>::toJSI(runtime, "CALL_ANSWERED");
|
|
@@ -102,6 +105,7 @@ namespace margelo::nitro {
|
|
|
102
105
|
case hashString("AUDIO_ROUTE_CHANGED"):
|
|
103
106
|
case hashString("CALL_HELD"):
|
|
104
107
|
case hashString("CALL_UNHELD"):
|
|
108
|
+
case hashString("CALL_UNHOLD_FAILED"):
|
|
105
109
|
case hashString("CALL_MUTED"):
|
|
106
110
|
case hashString("CALL_UNMUTED"):
|
|
107
111
|
case hashString("CALL_ANSWERED"):
|
package/package.json
CHANGED