@qusaieilouti99/call-manager 0.1.149 → 0.1.152
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 +345 -199
- package/android/src/main/java/com/margelo/nitro/qusaieilouti99/callmanager/MyConnection.kt +10 -5
- package/ios/CallManager.swift +17 -0
- package/lib/module/CallManager.nitro.js.map +1 -1
- package/lib/typescript/src/CallManager.nitro.d.ts.map +1 -1
- package/package.json +21 -15
- package/src/CallManager.nitro.ts +21 -8
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
package com.margelo.nitro.qusaieilouti99.callmanager
|
|
2
|
-
|
|
2
|
+
import android.telecom.CallAudioState
|
|
3
3
|
import android.app.ActivityManager
|
|
4
4
|
import android.app.Notification
|
|
5
5
|
import android.app.NotificationChannel
|
|
@@ -40,8 +40,11 @@ import android.os.VibrationEffect
|
|
|
40
40
|
* Core call‐management engine. Manages self-managed telecom calls,
|
|
41
41
|
* audio routing, UI notifications, etc.
|
|
42
42
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
43
|
+
* Audio routing follows Android standards:
|
|
44
|
+
* - Audio calls default to earpiece unless BT/headset connected
|
|
45
|
+
* - Video calls default to speaker unless BT/headset connected
|
|
46
|
+
* - System handles route changes when devices connect/disconnect
|
|
47
|
+
* - Manual route changes are always respected
|
|
45
48
|
*/
|
|
46
49
|
object CallEngine {
|
|
47
50
|
private const val TAG = "CallEngine"
|
|
@@ -78,20 +81,17 @@ object CallEngine {
|
|
|
78
81
|
private val telecomConnections = ConcurrentHashMap<String, Connection>()
|
|
79
82
|
private val callMetadata = ConcurrentHashMap<String, String>()
|
|
80
83
|
|
|
81
|
-
// NEW: Track incoming calls to prevent duplicates
|
|
82
|
-
private val incomingCallIds = ConcurrentHashMap<String, Long>()
|
|
83
|
-
|
|
84
84
|
private var currentCallId: String? = null
|
|
85
85
|
private var canMakeMultipleCalls: Boolean = false
|
|
86
|
-
private var lastAudioRoutesInfo: AudioRoutesInfo? = null
|
|
87
86
|
private var lockScreenBypassActive = false
|
|
88
87
|
private val lockScreenBypassCallbacks = mutableSetOf<LockScreenBypassCallback>()
|
|
89
88
|
private var eventHandler: ((CallEventType, String) -> Unit)? = null
|
|
90
89
|
private val cachedEvents = mutableListOf<Pair<CallEventType, String>>()
|
|
91
90
|
|
|
92
|
-
//
|
|
93
|
-
private var
|
|
94
|
-
private
|
|
91
|
+
// Audio routing state
|
|
92
|
+
private var currentAudioRoute: String = "Earpiece"
|
|
93
|
+
private var wasManuallySet: Boolean = false
|
|
94
|
+
private var callStartTime: Long = 0
|
|
95
95
|
|
|
96
96
|
interface LockScreenBypassCallback {
|
|
97
97
|
fun onLockScreenBypassChanged(shouldBypass: Boolean)
|
|
@@ -118,9 +118,6 @@ object CallEngine {
|
|
|
118
118
|
)
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
/**
|
|
122
|
-
* Get the application context. Returns null if not initialized.
|
|
123
|
-
*/
|
|
124
121
|
fun getContext(): Context? = appContext
|
|
125
122
|
|
|
126
123
|
fun setEventHandler(handler: ((CallEventType, String) -> Unit)?) {
|
|
@@ -146,16 +143,12 @@ object CallEngine {
|
|
|
146
143
|
}
|
|
147
144
|
}
|
|
148
145
|
|
|
149
|
-
/**
|
|
150
|
-
* NEW: Check if device supports CallStyle notifications
|
|
151
|
-
*/
|
|
152
146
|
private fun supportsCallStyleNotifications(): Boolean {
|
|
153
147
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return false
|
|
154
148
|
|
|
155
149
|
val manufacturer = Build.MANUFACTURER.lowercase()
|
|
156
150
|
val brand = Build.BRAND.lowercase()
|
|
157
151
|
|
|
158
|
-
// Known good manufacturers that support CallStyle properly
|
|
159
152
|
val supportedManufacturers = setOf(
|
|
160
153
|
"google", "samsung", "oneplus", "motorola", "sony", "lg", "htc"
|
|
161
154
|
)
|
|
@@ -173,10 +166,6 @@ object CallEngine {
|
|
|
173
166
|
return isSupported
|
|
174
167
|
}
|
|
175
168
|
|
|
176
|
-
/**
|
|
177
|
-
* Silences the incoming call ringtone. This is called by `Connection.onSilence()`
|
|
178
|
-
* when the user presses a volume key during ringing.
|
|
179
|
-
*/
|
|
180
169
|
fun silenceIncomingCall() {
|
|
181
170
|
Log.d(TAG, "Silencing incoming call ringtone via Connection.onSilence()")
|
|
182
171
|
stopRingtone()
|
|
@@ -245,19 +234,6 @@ object CallEngine {
|
|
|
245
234
|
initialize(context)
|
|
246
235
|
}
|
|
247
236
|
|
|
248
|
-
// NEW: Guard against duplicate calls
|
|
249
|
-
val currentTime = System.currentTimeMillis()
|
|
250
|
-
val lastCallTime = incomingCallIds[callId]
|
|
251
|
-
if (lastCallTime != null && (currentTime - lastCallTime) < 5000) {
|
|
252
|
-
Log.w(TAG, "Ignoring duplicate incoming call for callId: $callId (last call ${currentTime - lastCallTime}ms ago)")
|
|
253
|
-
return
|
|
254
|
-
}
|
|
255
|
-
incomingCallIds[callId] = currentTime
|
|
256
|
-
|
|
257
|
-
// Clean up old entries (older than 30 seconds)
|
|
258
|
-
val cutoffTime = currentTime - 30000
|
|
259
|
-
incomingCallIds.entries.removeAll { it.value < cutoffTime }
|
|
260
|
-
|
|
261
237
|
Log.d(TAG, "reportIncomingCall: callId=$callId, type=$callType, name=$displayName")
|
|
262
238
|
metadata?.let { callMetadata[callId] = it }
|
|
263
239
|
|
|
@@ -411,6 +387,8 @@ object CallEngine {
|
|
|
411
387
|
activeCalls[callId] =
|
|
412
388
|
CallInfo(callId, callType, targetName, null, CallState.ACTIVE)
|
|
413
389
|
currentCallId = callId
|
|
390
|
+
callStartTime = System.currentTimeMillis()
|
|
391
|
+
wasManuallySet = false
|
|
414
392
|
Log.d(TAG, "Call $callId started as ACTIVE")
|
|
415
393
|
|
|
416
394
|
registerPhoneAccount()
|
|
@@ -419,9 +397,12 @@ object CallEngine {
|
|
|
419
397
|
startForegroundService()
|
|
420
398
|
keepScreenAwake(true)
|
|
421
399
|
|
|
422
|
-
//
|
|
400
|
+
// Register audio device callback to handle dynamic device changes
|
|
401
|
+
registerAudioDeviceCallback()
|
|
402
|
+
|
|
403
|
+
// Set initial audio route based on call type and available devices
|
|
423
404
|
mainHandler.postDelayed({
|
|
424
|
-
setInitialAudioRoute(callType
|
|
405
|
+
setInitialAudioRoute(callType)
|
|
425
406
|
}, 500L)
|
|
426
407
|
|
|
427
408
|
updateLockScreenBypass()
|
|
@@ -448,6 +429,8 @@ object CallEngine {
|
|
|
448
429
|
|
|
449
430
|
activeCalls[callId] = callInfo.copy(state = CallState.ACTIVE)
|
|
450
431
|
currentCallId = callId
|
|
432
|
+
callStartTime = System.currentTimeMillis()
|
|
433
|
+
wasManuallySet = false
|
|
451
434
|
Log.d(TAG, "Call $callId set to ACTIVE state")
|
|
452
435
|
|
|
453
436
|
stopRingtone()
|
|
@@ -469,20 +452,12 @@ object CallEngine {
|
|
|
469
452
|
|
|
470
453
|
setAudioMode()
|
|
471
454
|
|
|
472
|
-
//
|
|
473
|
-
|
|
474
|
-
setInitialAudioRoute(callInfo.callType, isCallStart = true)
|
|
455
|
+
// Register audio device callback to handle dynamic device changes
|
|
456
|
+
registerAudioDeviceCallback()
|
|
475
457
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
val currentRoute = getCurrentAudioRoute()
|
|
480
|
-
if (currentRoute != "Speaker") {
|
|
481
|
-
Log.d(TAG, "Retrying audio route for video call - current: $currentRoute")
|
|
482
|
-
setAudioRoute("Speaker")
|
|
483
|
-
}
|
|
484
|
-
}, 1000L)
|
|
485
|
-
}
|
|
458
|
+
// Set initial audio route with proper timing
|
|
459
|
+
mainHandler.postDelayed({
|
|
460
|
+
setInitialAudioRoute(callInfo.callType)
|
|
486
461
|
}, 800L)
|
|
487
462
|
|
|
488
463
|
if (isLocalAnswer) {
|
|
@@ -641,7 +616,6 @@ object CallEngine {
|
|
|
641
616
|
activeCalls.clear()
|
|
642
617
|
telecomConnections.clear()
|
|
643
618
|
callMetadata.clear()
|
|
644
|
-
incomingCallIds.clear() // NEW: Clear duplicate tracking
|
|
645
619
|
currentCallId = null
|
|
646
620
|
|
|
647
621
|
cleanup()
|
|
@@ -658,7 +632,6 @@ object CallEngine {
|
|
|
658
632
|
|
|
659
633
|
val metadata = callMetadata.remove(callId)
|
|
660
634
|
activeCalls.remove(callId)
|
|
661
|
-
incomingCallIds.remove(callId) // NEW: Clean up duplicate tracking
|
|
662
635
|
|
|
663
636
|
stopRingback()
|
|
664
637
|
stopRingtone()
|
|
@@ -714,146 +687,262 @@ object CallEngine {
|
|
|
714
687
|
})
|
|
715
688
|
}
|
|
716
689
|
|
|
690
|
+
// ====== IMPROVED AUDIO ROUTING SYSTEM ======
|
|
691
|
+
|
|
717
692
|
fun getAudioDevices(): AudioRoutesInfo {
|
|
718
693
|
val context = requireContext()
|
|
719
694
|
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
|
|
720
695
|
?: return AudioRoutesInfo(emptyArray(), "Unknown")
|
|
721
696
|
|
|
722
697
|
val devices = mutableSetOf<String>()
|
|
723
|
-
|
|
698
|
+
var hasWiredHeadset = false
|
|
699
|
+
var hasBluetoothDevice = false
|
|
700
|
+
|
|
701
|
+
// Always available
|
|
724
702
|
devices.add("Earpiece")
|
|
703
|
+
devices.add("Speaker")
|
|
725
704
|
|
|
726
705
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
727
|
-
val
|
|
728
|
-
|
|
729
|
-
when (
|
|
706
|
+
val outputDevices = audioManager?.getDevices(AudioManager.GET_DEVICES_OUTPUTS) ?: emptyArray()
|
|
707
|
+
for (device in outputDevices) {
|
|
708
|
+
when (device.type) {
|
|
730
709
|
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
|
|
731
|
-
AudioDeviceInfo.TYPE_BLUETOOTH_SCO ->
|
|
710
|
+
AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> {
|
|
711
|
+
if (!hasBluetoothDevice) {
|
|
712
|
+
devices.add("Bluetooth")
|
|
713
|
+
hasBluetoothDevice = true
|
|
714
|
+
}
|
|
715
|
+
}
|
|
732
716
|
AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
|
|
733
|
-
AudioDeviceInfo.TYPE_WIRED_HEADSET
|
|
717
|
+
AudioDeviceInfo.TYPE_WIRED_HEADSET,
|
|
718
|
+
AudioDeviceInfo.TYPE_USB_HEADSET -> {
|
|
719
|
+
if (!hasWiredHeadset) {
|
|
720
|
+
devices.add("Headset")
|
|
721
|
+
hasWiredHeadset = true
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
AudioDeviceInfo.TYPE_BLE_HEADSET -> {
|
|
725
|
+
if (!hasBluetoothDevice) {
|
|
726
|
+
devices.add("Bluetooth")
|
|
727
|
+
hasBluetoothDevice = true
|
|
728
|
+
}
|
|
729
|
+
}
|
|
734
730
|
}
|
|
735
731
|
}
|
|
736
732
|
} else {
|
|
733
|
+
// Fallback for older API levels
|
|
737
734
|
@Suppress("DEPRECATION")
|
|
738
|
-
if (audioManager?.isBluetoothA2dpOn == true || audioManager?.isBluetoothScoOn == true)
|
|
735
|
+
if (audioManager?.isBluetoothA2dpOn == true || audioManager?.isBluetoothScoOn == true) {
|
|
739
736
|
devices.add("Bluetooth")
|
|
737
|
+
hasBluetoothDevice = true
|
|
738
|
+
}
|
|
740
739
|
@Suppress("DEPRECATION")
|
|
741
|
-
if (audioManager?.isWiredHeadsetOn == true)
|
|
740
|
+
if (audioManager?.isWiredHeadsetOn == true) {
|
|
741
|
+
devices.add("Headset")
|
|
742
|
+
hasWiredHeadset = true
|
|
743
|
+
}
|
|
742
744
|
}
|
|
743
745
|
|
|
744
746
|
val current = getCurrentAudioRoute()
|
|
745
747
|
Log.d(TAG, "Available audio devices: ${devices.toList()}, current: $current")
|
|
746
748
|
|
|
747
|
-
// Convert strings to StringHolder objects
|
|
748
749
|
val deviceHolders = devices.map { StringHolder(it) }.toTypedArray()
|
|
749
|
-
lastAudioRoutesInfo = AudioRoutesInfo(deviceHolders, current)
|
|
750
750
|
return AudioRoutesInfo(deviceHolders, current)
|
|
751
751
|
}
|
|
752
752
|
|
|
753
|
-
|
|
754
|
-
|
|
753
|
+
// NEW: Handle telecom audio route changes
|
|
754
|
+
fun onTelecomAudioRouteChanged(callId: String, audioState: CallAudioState) {
|
|
755
|
+
Log.d(TAG, "Telecom audio route changed for $callId: route=${audioState.route}")
|
|
756
|
+
|
|
757
|
+
val routeString = when (audioState.route) {
|
|
758
|
+
CallAudioState.ROUTE_EARPIECE -> "Earpiece"
|
|
759
|
+
CallAudioState.ROUTE_SPEAKER -> "Speaker"
|
|
760
|
+
CallAudioState.ROUTE_BLUETOOTH -> "Bluetooth"
|
|
761
|
+
CallAudioState.ROUTE_WIRED_HEADSET -> "Headset"
|
|
762
|
+
else -> "Unknown"
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
Log.d(TAG, "Emitting AUDIO_ROUTE_CHANGED: currentRoute=$routeString")
|
|
766
|
+
emitAudioRouteChanged(routeString)
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// NEW: Set initial audio route using telecom
|
|
770
|
+
fun setInitialAudioRouteForCall(callId: String, callType: String) {
|
|
771
|
+
Log.d(TAG, "Setting initial audio route for $callId, type: $callType")
|
|
755
772
|
|
|
773
|
+
// Determine the desired route
|
|
774
|
+
val desiredRoute = when {
|
|
775
|
+
isBluetoothDeviceConnected() -> CallAudioState.ROUTE_BLUETOOTH
|
|
776
|
+
isWiredHeadsetConnected() -> CallAudioState.ROUTE_WIRED_HEADSET
|
|
777
|
+
callType.equals("Video", ignoreCase = true) -> CallAudioState.ROUTE_SPEAKER
|
|
778
|
+
else -> CallAudioState.ROUTE_EARPIECE
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Use telecom connection to set the route
|
|
782
|
+
telecomConnections[callId]?.let { connection ->
|
|
783
|
+
if (connection is MyConnection) {
|
|
784
|
+
mainHandler.postDelayed({
|
|
785
|
+
connection.setTelecomAudioRoute(desiredRoute)
|
|
786
|
+
Log.d(TAG, "Set initial telecom audio route to: $desiredRoute")
|
|
787
|
+
}, 200)
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// UPDATED: Use telecom for manual route changes
|
|
793
|
+
fun setAudioRoute(route: String) {
|
|
794
|
+
Log.d(TAG, "setAudioRoute called: $route (manual)")
|
|
795
|
+
wasManuallySet = true
|
|
796
|
+
|
|
797
|
+
val telecomRoute = when (route) {
|
|
798
|
+
"Speaker" -> CallAudioState.ROUTE_SPEAKER
|
|
799
|
+
"Earpiece" -> CallAudioState.ROUTE_EARPIECE
|
|
800
|
+
"Bluetooth" -> CallAudioState.ROUTE_BLUETOOTH
|
|
801
|
+
"Headset" -> CallAudioState.ROUTE_WIRED_HEADSET
|
|
802
|
+
else -> {
|
|
803
|
+
Log.w(TAG, "Unknown audio route: $route")
|
|
804
|
+
return
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Set route through active telecom connection
|
|
809
|
+
currentCallId?.let { callId ->
|
|
810
|
+
telecomConnections[callId]?.let { connection ->
|
|
811
|
+
if (connection is MyConnection) {
|
|
812
|
+
connection.setTelecomAudioRoute(telecomRoute)
|
|
813
|
+
Log.d(TAG, "Set telecom audio route to: $telecomRoute for $route")
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
private fun applyAudioRoute(route: String) {
|
|
756
820
|
val ctx = requireContext()
|
|
757
821
|
if (audioManager == null) {
|
|
758
822
|
audioManager = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
759
823
|
}
|
|
760
824
|
val am = audioManager!!
|
|
761
825
|
|
|
826
|
+
// Ensure we're in the correct audio mode
|
|
762
827
|
if (am.mode != AudioManager.MODE_IN_COMMUNICATION) {
|
|
763
828
|
am.mode = AudioManager.MODE_IN_COMMUNICATION
|
|
764
829
|
}
|
|
765
830
|
|
|
831
|
+
val previousRoute = currentAudioRoute
|
|
832
|
+
|
|
766
833
|
when (route) {
|
|
767
834
|
"Speaker" -> {
|
|
768
835
|
am.isSpeakerphoneOn = true
|
|
769
|
-
if (
|
|
836
|
+
if (am.isBluetoothScoOn) {
|
|
770
837
|
am.stopBluetoothSco()
|
|
771
838
|
am.isBluetoothScoOn = false
|
|
772
839
|
}
|
|
773
|
-
|
|
840
|
+
currentAudioRoute = "Speaker"
|
|
841
|
+
Log.d(TAG, "Audio route set to SPEAKER")
|
|
774
842
|
}
|
|
775
843
|
"Earpiece" -> {
|
|
776
844
|
am.isSpeakerphoneOn = false
|
|
777
|
-
if (
|
|
845
|
+
if (am.isBluetoothScoOn) {
|
|
778
846
|
am.stopBluetoothSco()
|
|
779
847
|
am.isBluetoothScoOn = false
|
|
780
848
|
}
|
|
781
|
-
|
|
849
|
+
currentAudioRoute = "Earpiece"
|
|
850
|
+
Log.d(TAG, "Audio route set to EARPIECE")
|
|
782
851
|
}
|
|
783
852
|
"Bluetooth" -> {
|
|
784
853
|
am.isSpeakerphoneOn = false
|
|
785
|
-
if (
|
|
854
|
+
if (!am.isBluetoothScoOn) {
|
|
786
855
|
am.startBluetoothSco()
|
|
787
856
|
am.isBluetoothScoOn = true
|
|
788
|
-
Log.d(TAG, "Audio routed to BLUETOOTH")
|
|
789
|
-
} else {
|
|
790
|
-
Log.w(TAG, "Bluetooth SCO not supported on this OS version")
|
|
791
857
|
}
|
|
858
|
+
currentAudioRoute = "Bluetooth"
|
|
859
|
+
Log.d(TAG, "Audio route set to BLUETOOTH")
|
|
792
860
|
}
|
|
793
861
|
"Headset" -> {
|
|
794
862
|
am.isSpeakerphoneOn = false
|
|
795
|
-
if (
|
|
863
|
+
if (am.isBluetoothScoOn) {
|
|
796
864
|
am.stopBluetoothSco()
|
|
797
865
|
am.isBluetoothScoOn = false
|
|
798
866
|
}
|
|
799
|
-
|
|
867
|
+
// For wired headsets, the system automatically routes audio when connected
|
|
868
|
+
currentAudioRoute = "Headset"
|
|
869
|
+
Log.d(TAG, "Audio route set to HEADSET")
|
|
800
870
|
}
|
|
801
871
|
else -> {
|
|
802
872
|
Log.w(TAG, "Unknown audio route: $route")
|
|
803
873
|
return
|
|
804
874
|
}
|
|
805
875
|
}
|
|
806
|
-
|
|
876
|
+
|
|
877
|
+
// Only emit event if route actually changed
|
|
878
|
+
if (currentAudioRoute != previousRoute) {
|
|
879
|
+
emitAudioRouteChanged(currentAudioRoute)
|
|
880
|
+
}
|
|
807
881
|
}
|
|
808
882
|
|
|
809
883
|
private fun getCurrentAudioRoute(): String {
|
|
884
|
+
val am = audioManager ?: return "Unknown"
|
|
885
|
+
|
|
886
|
+
// Check in order of priority: Bluetooth -> Headset -> Speaker -> Earpiece
|
|
810
887
|
return when {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
888
|
+
am.isBluetoothScoOn -> "Bluetooth"
|
|
889
|
+
isWiredHeadsetConnected() -> "Headset"
|
|
890
|
+
am.isSpeakerphoneOn -> "Speaker"
|
|
814
891
|
else -> "Earpiece"
|
|
815
892
|
}
|
|
816
893
|
}
|
|
817
894
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
val avail = getAudioDevices()
|
|
821
|
-
// Extract string values for comparison
|
|
822
|
-
val deviceStrings = avail.devices.map { it.value }
|
|
895
|
+
private fun isWiredHeadsetConnected(): Boolean {
|
|
896
|
+
val am = audioManager ?: return false
|
|
823
897
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
898
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
899
|
+
val devices = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
|
|
900
|
+
return devices.any { device ->
|
|
901
|
+
device.type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
|
|
902
|
+
device.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES ||
|
|
903
|
+
device.type == AudioDeviceInfo.TYPE_USB_HEADSET
|
|
904
|
+
}
|
|
905
|
+
} else {
|
|
906
|
+
@Suppress("DEPRECATION")
|
|
907
|
+
return am.isWiredHeadsetOn
|
|
829
908
|
}
|
|
909
|
+
}
|
|
830
910
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
// For call start, ensure audio mode is properly set first
|
|
834
|
-
if (isCallStart) {
|
|
835
|
-
setAudioMode()
|
|
911
|
+
private fun isBluetoothDeviceConnected(): Boolean {
|
|
912
|
+
val am = audioManager ?: return false
|
|
836
913
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
mainHandler.postDelayed({
|
|
844
|
-
val currentRoute = getCurrentAudioRoute()
|
|
845
|
-
if (currentRoute != "Speaker" && !deviceStrings.contains("Bluetooth") && !deviceStrings.contains("Headset")) {
|
|
846
|
-
Log.d(TAG, "Forcing speaker for video call - current route was: $currentRoute")
|
|
847
|
-
setAudioRoute("Speaker")
|
|
848
|
-
}
|
|
849
|
-
}, 300L)
|
|
850
|
-
}, 200L)
|
|
851
|
-
} else {
|
|
852
|
-
setAudioRoute(defaultRoute)
|
|
914
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
915
|
+
val devices = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
|
|
916
|
+
return devices.any { device ->
|
|
917
|
+
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP ||
|
|
918
|
+
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO ||
|
|
919
|
+
device.type == AudioDeviceInfo.TYPE_BLE_HEADSET
|
|
853
920
|
}
|
|
854
921
|
} else {
|
|
855
|
-
|
|
922
|
+
@Suppress("DEPRECATION")
|
|
923
|
+
return am.isBluetoothA2dpOn || am.isBluetoothScoOn
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
private fun setInitialAudioRoute(callType: String) {
|
|
928
|
+
Log.d(TAG, "Setting initial audio route for call type: $callType")
|
|
929
|
+
|
|
930
|
+
// Don't override if user manually set a route
|
|
931
|
+
if (wasManuallySet) {
|
|
932
|
+
Log.d(TAG, "Audio route was manually set, skipping initial route")
|
|
933
|
+
return
|
|
856
934
|
}
|
|
935
|
+
|
|
936
|
+
// Determine default route based on Android standards
|
|
937
|
+
val defaultRoute = when {
|
|
938
|
+
isBluetoothDeviceConnected() -> "Bluetooth"
|
|
939
|
+
isWiredHeadsetConnected() -> "Headset"
|
|
940
|
+
callType.equals("Video", ignoreCase = true) -> "Speaker"
|
|
941
|
+
else -> "Earpiece" // Default for audio calls
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
Log.d(TAG, "Setting initial audio route to: $defaultRoute")
|
|
945
|
+
applyAudioRoute(defaultRoute)
|
|
857
946
|
}
|
|
858
947
|
|
|
859
948
|
private fun setAudioMode() {
|
|
@@ -863,40 +952,124 @@ object CallEngine {
|
|
|
863
952
|
|
|
864
953
|
private fun resetAudioMode() {
|
|
865
954
|
if (activeCalls.isEmpty()) {
|
|
866
|
-
audioManager?.
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
955
|
+
audioManager?.let { am ->
|
|
956
|
+
am.mode = AudioManager.MODE_NORMAL
|
|
957
|
+
if (am.isBluetoothScoOn) {
|
|
958
|
+
am.stopBluetoothSco()
|
|
959
|
+
am.isBluetoothScoOn = false
|
|
960
|
+
}
|
|
961
|
+
am.isSpeakerphoneOn = false
|
|
962
|
+
}
|
|
963
|
+
currentAudioRoute = "Earpiece"
|
|
964
|
+
wasManuallySet = false
|
|
965
|
+
unregisterAudioDeviceCallback()
|
|
870
966
|
Log.d(TAG, "Audio mode reset to MODE_NORMAL")
|
|
871
967
|
}
|
|
872
968
|
}
|
|
873
969
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
970
|
+
// UPDATED: Fix the method signature
|
|
971
|
+
private fun emitAudioRouteChanged(currentRoute: String) {
|
|
972
|
+
val info = getAudioDevices()
|
|
973
|
+
val deviceStrings = info.devices.map { it.value }
|
|
974
|
+
val payload = JSONObject().apply {
|
|
975
|
+
put("devices", JSONArray(deviceStrings))
|
|
976
|
+
put("currentRoute", currentRoute)
|
|
977
|
+
}
|
|
978
|
+
emitEvent(CallEventType.AUDIO_ROUTE_CHANGED, payload)
|
|
979
|
+
Log.d(TAG, "Audio route changed: $currentRoute, available: $deviceStrings")
|
|
884
980
|
}
|
|
885
981
|
|
|
886
982
|
private val audioDeviceCallback = object : AudioDeviceCallback() {
|
|
887
983
|
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
|
|
888
984
|
Log.d(TAG, "Audio devices added")
|
|
889
|
-
|
|
985
|
+
handleAudioDeviceChange(addedDevices, true)
|
|
890
986
|
}
|
|
987
|
+
|
|
891
988
|
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>?) {
|
|
892
989
|
Log.d(TAG, "Audio devices removed")
|
|
893
|
-
|
|
990
|
+
handleAudioDeviceChange(removedDevices, false)
|
|
894
991
|
}
|
|
895
992
|
}
|
|
896
993
|
|
|
994
|
+
private fun handleAudioDeviceChange(devices: Array<out AudioDeviceInfo>?, isAdded: Boolean) {
|
|
995
|
+
if (devices == null || !isCallActive()) return
|
|
996
|
+
|
|
997
|
+
val context = requireContext()
|
|
998
|
+
val currentCallInfo = getCurrentActiveCall()
|
|
999
|
+
if (currentCallInfo == null) {
|
|
1000
|
+
Log.d(TAG, "No active call, ignoring device change")
|
|
1001
|
+
return
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
val relevantDevices = devices.filter { device ->
|
|
1005
|
+
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP ||
|
|
1006
|
+
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO ||
|
|
1007
|
+
device.type == AudioDeviceInfo.TYPE_BLE_HEADSET ||
|
|
1008
|
+
device.type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
|
|
1009
|
+
device.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES ||
|
|
1010
|
+
device.type == AudioDeviceInfo.TYPE_USB_HEADSET
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
if (relevantDevices.isEmpty()) {
|
|
1014
|
+
Log.d(TAG, "No relevant devices in change event")
|
|
1015
|
+
return
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
Log.d(TAG, "Relevant device change detected. Added: $isAdded, wasManuallySet: $wasManuallySet")
|
|
1019
|
+
|
|
1020
|
+
if (isAdded && !wasManuallySet) {
|
|
1021
|
+
// Device connected - switch to it automatically if user hasn't manually set route
|
|
1022
|
+
val deviceType = relevantDevices.first().type
|
|
1023
|
+
val newRoute = when (deviceType) {
|
|
1024
|
+
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
|
|
1025
|
+
AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
|
|
1026
|
+
AudioDeviceInfo.TYPE_BLE_HEADSET -> "Bluetooth"
|
|
1027
|
+
AudioDeviceInfo.TYPE_WIRED_HEADSET,
|
|
1028
|
+
AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
|
|
1029
|
+
AudioDeviceInfo.TYPE_USB_HEADSET -> "Headset"
|
|
1030
|
+
else -> null
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
if (newRoute != null && newRoute != currentAudioRoute) {
|
|
1034
|
+
Log.d(TAG, "Auto-switching to newly connected device: $newRoute")
|
|
1035
|
+
// Add slight delay to ensure device is ready
|
|
1036
|
+
mainHandler.postDelayed({
|
|
1037
|
+
applyAudioRoute(newRoute)
|
|
1038
|
+
}, 300)
|
|
1039
|
+
}
|
|
1040
|
+
} else if (!isAdded) {
|
|
1041
|
+
// Device disconnected - fall back to appropriate route
|
|
1042
|
+
val disconnectedType = relevantDevices.first().type
|
|
1043
|
+
val wasCurrentRoute = when (disconnectedType) {
|
|
1044
|
+
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
|
|
1045
|
+
AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
|
|
1046
|
+
AudioDeviceInfo.TYPE_BLE_HEADSET -> currentAudioRoute == "Bluetooth"
|
|
1047
|
+
AudioDeviceInfo.TYPE_WIRED_HEADSET,
|
|
1048
|
+
AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
|
|
1049
|
+
AudioDeviceInfo.TYPE_USB_HEADSET -> currentAudioRoute == "Headset"
|
|
1050
|
+
else -> false
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if (wasCurrentRoute) {
|
|
1054
|
+
Log.d(TAG, "Current audio device disconnected, falling back")
|
|
1055
|
+
// Reset manual flag since the manually selected device is gone
|
|
1056
|
+
wasManuallySet = false
|
|
1057
|
+
mainHandler.postDelayed({
|
|
1058
|
+
setInitialAudioRoute(currentCallInfo.callType)
|
|
1059
|
+
}, 300)
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// Always emit devices changed event
|
|
1064
|
+
emitAudioDevicesChanged()
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
private fun getCurrentActiveCall(): CallInfo? {
|
|
1068
|
+
return activeCalls.values.find { it.state == CallState.ACTIVE }
|
|
1069
|
+
}
|
|
1070
|
+
|
|
897
1071
|
private fun emitAudioDevicesChanged() {
|
|
898
1072
|
val info = getAudioDevices()
|
|
899
|
-
// Extract string values from StringHolder objects
|
|
900
1073
|
val deviceStrings = info.devices.map { it.value }
|
|
901
1074
|
val payload = JSONObject().apply {
|
|
902
1075
|
put("devices", JSONArray(deviceStrings))
|
|
@@ -907,17 +1080,29 @@ object CallEngine {
|
|
|
907
1080
|
}
|
|
908
1081
|
|
|
909
1082
|
fun registerAudioDeviceCallback() {
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1083
|
+
if (isCallActive()) {
|
|
1084
|
+
val context = requireContext()
|
|
1085
|
+
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
1086
|
+
try {
|
|
1087
|
+
audioManager?.registerAudioDeviceCallback(audioDeviceCallback, null)
|
|
1088
|
+
Log.d(TAG, "Audio device callback registered")
|
|
1089
|
+
} catch (e: Exception) {
|
|
1090
|
+
Log.w(TAG, "Failed to register audio device callback: ${e.message}")
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
913
1093
|
}
|
|
914
1094
|
|
|
915
1095
|
fun unregisterAudioDeviceCallback() {
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1096
|
+
try {
|
|
1097
|
+
audioManager?.unregisterAudioDeviceCallback(audioDeviceCallback)
|
|
1098
|
+
Log.d(TAG, "Audio device callback unregistered")
|
|
1099
|
+
} catch (e: Exception) {
|
|
1100
|
+
Log.w(TAG, "Failed to unregister audio device callback: ${e.message}")
|
|
1101
|
+
}
|
|
919
1102
|
}
|
|
920
1103
|
|
|
1104
|
+
// ====== END AUDIO ROUTING SYSTEM ======
|
|
1105
|
+
|
|
921
1106
|
fun keepScreenAwake(keepAwake: Boolean) {
|
|
922
1107
|
val context = requireContext()
|
|
923
1108
|
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
|
@@ -958,7 +1143,6 @@ object CallEngine {
|
|
|
958
1143
|
|
|
959
1144
|
private fun rejectIncomingCallCollision(callId: String, reason: String) {
|
|
960
1145
|
callMetadata.remove(callId)
|
|
961
|
-
incomingCallIds.remove(callId) // NEW: Clean up duplicate tracking
|
|
962
1146
|
emitEvent(CallEventType.CALL_REJECTED, JSONObject().apply {
|
|
963
1147
|
put("callId", callId)
|
|
964
1148
|
put("reason", reason)
|
|
@@ -980,7 +1164,6 @@ object CallEngine {
|
|
|
980
1164
|
channel.setBypassDnd(true)
|
|
981
1165
|
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
|
982
1166
|
|
|
983
|
-
// NEW: Improved sound handling to prevent double ringing
|
|
984
1167
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
985
1168
|
channel.setSound(
|
|
986
1169
|
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE),
|
|
@@ -990,7 +1173,6 @@ object CallEngine {
|
|
|
990
1173
|
.build()
|
|
991
1174
|
)
|
|
992
1175
|
} else {
|
|
993
|
-
// For API 31+, disable notification sound to prevent conflicts with custom ringtone
|
|
994
1176
|
channel.setSound(null, null)
|
|
995
1177
|
channel.importance = NotificationManager.IMPORTANCE_HIGH
|
|
996
1178
|
}
|
|
@@ -1013,8 +1195,6 @@ object CallEngine {
|
|
|
1013
1195
|
Log.d(TAG, "Device is unlocked and supports CallStyle - using enhanced notification")
|
|
1014
1196
|
showStandardNotification(context, callId, callerName, callType, callerPicUrl)
|
|
1015
1197
|
}
|
|
1016
|
-
|
|
1017
|
-
// NEW: Improved ringtone handling to prevent double ringing
|
|
1018
1198
|
playRingtone()
|
|
1019
1199
|
}
|
|
1020
1200
|
|
|
@@ -1258,73 +1438,43 @@ object CallEngine {
|
|
|
1258
1438
|
)
|
|
1259
1439
|
}
|
|
1260
1440
|
|
|
1261
|
-
// NEW: Improved ringtone handling to prevent double ringing
|
|
1262
1441
|
private fun playRingtone() {
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
return
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
val context = requireContext()
|
|
1270
|
-
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
1271
|
-
|
|
1272
|
-
// Only set ringtone mode if not already in communication mode
|
|
1273
|
-
val currentMode = audioManager?.mode ?: AudioManager.MODE_NORMAL
|
|
1274
|
-
if (currentMode != AudioManager.MODE_IN_COMMUNICATION) {
|
|
1275
|
-
audioManager?.mode = AudioManager.MODE_RINGTONE
|
|
1276
|
-
}
|
|
1442
|
+
val context = requireContext()
|
|
1443
|
+
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
1444
|
+
audioManager?.mode = AudioManager.MODE_RINGTONE
|
|
1277
1445
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
}
|
|
1446
|
+
vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
|
|
1447
|
+
vibrator?.let { v ->
|
|
1448
|
+
val pattern = longArrayOf(0L, 500L, 500L)
|
|
1449
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
1450
|
+
v.vibrate(VibrationEffect.createWaveform(pattern, 0))
|
|
1451
|
+
} else {
|
|
1452
|
+
@Suppress("DEPRECATION")
|
|
1453
|
+
v.vibrate(pattern, 0)
|
|
1287
1454
|
}
|
|
1455
|
+
}
|
|
1288
1456
|
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
true
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
if (shouldPlayCustomRingtone) {
|
|
1300
|
-
val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
|
|
1301
|
-
ringtone = RingtoneManager.getRingtone(context, uri)
|
|
1302
|
-
ringtone?.play()
|
|
1303
|
-
isCustomRingtoneActive = true
|
|
1304
|
-
Log.d(TAG, "Custom ringtone started playing")
|
|
1305
|
-
} else {
|
|
1306
|
-
Log.d(TAG, "Skipping custom ringtone - system should handle it")
|
|
1307
|
-
}
|
|
1308
|
-
} catch (e: Exception) {
|
|
1309
|
-
Log.e(TAG, "Failed to play ringtone", e)
|
|
1310
|
-
}
|
|
1457
|
+
try {
|
|
1458
|
+
val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
|
|
1459
|
+
ringtone = RingtoneManager.getRingtone(context, uri)
|
|
1460
|
+
ringtone?.play()
|
|
1461
|
+
Log.d(TAG, "Ringtone started playing")
|
|
1462
|
+
} catch (e: Exception) {
|
|
1463
|
+
Log.e(TAG, "Failed to play ringtone", e)
|
|
1311
1464
|
}
|
|
1312
1465
|
}
|
|
1313
1466
|
|
|
1314
1467
|
fun stopRingtone() {
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
} catch (e: Exception) {
|
|
1321
|
-
Log.e(TAG, "Error stopping ringtone", e)
|
|
1322
|
-
}
|
|
1323
|
-
ringtone = null
|
|
1324
|
-
|
|
1325
|
-
vibrator?.cancel()
|
|
1326
|
-
vibrator = null
|
|
1468
|
+
try {
|
|
1469
|
+
ringtone?.stop()
|
|
1470
|
+
Log.d(TAG, "Ringtone stopped")
|
|
1471
|
+
} catch (e: Exception) {
|
|
1472
|
+
Log.e(TAG, "Error stopping ringtone", e)
|
|
1327
1473
|
}
|
|
1474
|
+
ringtone = null
|
|
1475
|
+
|
|
1476
|
+
vibrator?.cancel()
|
|
1477
|
+
vibrator = null
|
|
1328
1478
|
}
|
|
1329
1479
|
|
|
1330
1480
|
private fun startRingback() {
|
|
@@ -1360,9 +1510,6 @@ object CallEngine {
|
|
|
1360
1510
|
stopForegroundService()
|
|
1361
1511
|
keepScreenAwake(false)
|
|
1362
1512
|
resetAudioMode()
|
|
1363
|
-
synchronized(ringtoneStateLock) {
|
|
1364
|
-
isCustomRingtoneActive = false
|
|
1365
|
-
}
|
|
1366
1513
|
}
|
|
1367
1514
|
|
|
1368
1515
|
fun onApplicationTerminate() {
|
|
@@ -1376,7 +1523,6 @@ object CallEngine {
|
|
|
1376
1523
|
activeCalls.clear()
|
|
1377
1524
|
telecomConnections.clear()
|
|
1378
1525
|
callMetadata.clear()
|
|
1379
|
-
incomingCallIds.clear() // NEW: Clear duplicate tracking
|
|
1380
1526
|
currentCallId = null
|
|
1381
1527
|
cleanup()
|
|
1382
1528
|
lockScreenBypassCallbacks.clear()
|
|
@@ -40,16 +40,11 @@ class MyConnection(
|
|
|
40
40
|
Log.d(TAG, "MyConnection for callId $callId created and added to CallEngine. Type: $callType")
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
// --- THIS IS THE MISSING PIECE ---
|
|
44
|
-
/**
|
|
45
|
-
* Called by the system when the user presses a volume key during ringing.
|
|
46
|
-
*/
|
|
47
43
|
override fun onSilence() {
|
|
48
44
|
super.onSilence()
|
|
49
45
|
Log.d(TAG, "onSilence called by system for callId: $callId. Silencing ringtone.")
|
|
50
46
|
CallEngine.silenceIncomingCall()
|
|
51
47
|
}
|
|
52
|
-
// ---------------------------------
|
|
53
48
|
|
|
54
49
|
override fun onAnswer() {
|
|
55
50
|
Log.d(TAG, "Call answered via Telecom for callId: $callId")
|
|
@@ -97,6 +92,8 @@ class MyConnection(
|
|
|
97
92
|
|
|
98
93
|
if (lastAudioState == null || lastAudioState!!.route != state.route) {
|
|
99
94
|
Log.d(TAG, "System audio route changed for callId: $callId. Telecom route: ${state.route}")
|
|
95
|
+
// Notify CallEngine about the actual route change
|
|
96
|
+
CallEngine.onTelecomAudioRouteChanged(callId, state)
|
|
100
97
|
}
|
|
101
98
|
|
|
102
99
|
lastAudioState = state
|
|
@@ -134,6 +131,8 @@ class MyConnection(
|
|
|
134
131
|
}
|
|
135
132
|
STATE_ACTIVE -> {
|
|
136
133
|
Log.d(TAG, "Connection is now active for callId: $callId")
|
|
134
|
+
// Set initial audio route when call becomes active
|
|
135
|
+
CallEngine.setInitialAudioRouteForCall(callId, callType)
|
|
137
136
|
}
|
|
138
137
|
STATE_DISCONNECTED -> {
|
|
139
138
|
Log.d(TAG, "Connection is now disconnected for callId: $callId")
|
|
@@ -141,4 +140,10 @@ class MyConnection(
|
|
|
141
140
|
}
|
|
142
141
|
}
|
|
143
142
|
}
|
|
143
|
+
|
|
144
|
+
// NEW: Method to set audio route through telecom
|
|
145
|
+
fun setTelecomAudioRoute(route: Int) {
|
|
146
|
+
Log.d(TAG, "Setting telecom audio route to: $route for callId: $callId")
|
|
147
|
+
setAudioRoute(route)
|
|
148
|
+
}
|
|
144
149
|
}
|
package/ios/CallManager.swift
CHANGED
|
@@ -77,6 +77,23 @@ public class CallManager: HybridCallManagerSpec {
|
|
|
77
77
|
)
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
public func reportIncomingCall(callId: String,
|
|
81
|
+
callType: String,
|
|
82
|
+
targetName: String,
|
|
83
|
+
metadata: String?) throws
|
|
84
|
+
{
|
|
85
|
+
logger.info("🎯 startOutgoingCall ▶ js → native: \(callId), type=\(callType)")
|
|
86
|
+
if let m = metadata { logger.debug("🎯 metadata.len=\(m.count)") }
|
|
87
|
+
CallEngine.shared.reportIncomingCall(
|
|
88
|
+
callId: callId,
|
|
89
|
+
callType: callType,
|
|
90
|
+
targetName: targetName,
|
|
91
|
+
nil,
|
|
92
|
+
metadata: metadata
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
80
97
|
public func startCall(callId: String,
|
|
81
98
|
callType: String,
|
|
82
99
|
targetName: String,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["NitroModules","CallManagerHybridObject","createHybridObject"],"sourceRoot":"../../src","sources":["CallManager.nitro.ts"],"mappings":";;AAAA;AACA,SAA4BA,YAAY,QAAQ,4BAA4B;;
|
|
1
|
+
{"version":3,"names":["NitroModules","CallManagerHybridObject","createHybridObject"],"sourceRoot":"../../src","sources":["CallManager.nitro.ts"],"mappings":";;AAAA;AACA,SAA4BA,YAAY,QAAQ,4BAA4B;;AAG5E;;AAKA;;AAyDA,OAAO,MAAMC,uBAAuB,GAClCD,YAAY,CAACE,kBAAkB,CAAc,aAAa,CAAC","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CallManager.nitro.d.ts","sourceRoot":"","sources":["../../../src/CallManager.nitro.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAAgB,MAAM,4BAA4B,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"CallManager.nitro.d.ts","sourceRoot":"","sources":["../../../src/CallManager.nitro.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAAgB,MAAM,4BAA4B,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf;AAGD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WACf,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;IAEzD,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,IAAI,IAAI,CAAC;IACxB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAGnC,eAAe,IAAI,eAAe,CAAC;IACnC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,eAAe,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IAE1C,WAAW,IAAI,IAAI,CAAC;IACpB,iBAAiB,CACf,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAAC;IACR,SAAS,CACP,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAAC;IACR,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/C,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAGvE,kBAAkB,CAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAAC;IAGR,WAAW,CAET,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GACxD,MAAM,IAAI,CAAC;IAEd,yBAAyB,CAEvB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAClC,MAAM,IAAI,CAAC;IAEd,aAAa,IAAI,OAAO,CAAC;CAC1B;AAED,eAAO,MAAM,uBAAuB,aACyB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qusaieilouti99/call-manager",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.152",
|
|
4
4
|
"description": "Call manager",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -62,29 +62,35 @@
|
|
|
62
62
|
"registry": "https://registry.npmjs.org/"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@commitlint/config-conventional": "^19.
|
|
66
|
-
"@eslint/compat": "^1.
|
|
67
|
-
"@eslint/eslintrc": "^
|
|
68
|
-
"@eslint/js": "^
|
|
69
|
-
"@evilmartians/lefthook": "^1.
|
|
65
|
+
"@commitlint/config-conventional": "^19.6.0",
|
|
66
|
+
"@eslint/compat": "^1.2.7",
|
|
67
|
+
"@eslint/eslintrc": "^3.3.0",
|
|
68
|
+
"@eslint/js": "^9.22.0",
|
|
69
|
+
"@evilmartians/lefthook": "^1.5.0",
|
|
70
70
|
"@react-native/babel-preset": "0.79.2",
|
|
71
|
-
"@react-native/eslint-config": "^0.
|
|
71
|
+
"@react-native/eslint-config": "^0.78.0",
|
|
72
|
+
"@release-it/conventional-changelog": "^9.0.2",
|
|
72
73
|
"@types/jest": "^29.5.5",
|
|
73
74
|
"@types/react": "^19.0.0",
|
|
74
|
-
"
|
|
75
|
+
"@typescript-eslint/eslint-plugin": "^8.39.0",
|
|
76
|
+
"commitlint": "^19.6.1",
|
|
75
77
|
"del-cli": "^5.1.0",
|
|
76
|
-
"eslint": "^9.
|
|
77
|
-
"eslint-config-prettier": "^10.1.
|
|
78
|
-
"eslint-plugin-
|
|
78
|
+
"eslint": "^9.22.0",
|
|
79
|
+
"eslint-config-prettier": "^10.1.1",
|
|
80
|
+
"eslint-plugin-ft-flow": "^3.0.11",
|
|
81
|
+
"eslint-plugin-jest": "^29.0.1",
|
|
82
|
+
"eslint-plugin-prettier": "^5.2.3",
|
|
83
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
84
|
+
"eslint-plugin-react-native": "^5.0.0",
|
|
79
85
|
"jest": "^29.7.0",
|
|
80
86
|
"nitro-codegen": "*",
|
|
81
|
-
"prettier": "^3.
|
|
87
|
+
"prettier": "^3.0.3",
|
|
82
88
|
"react": "19.0.0",
|
|
83
89
|
"react-native": "0.79.2",
|
|
84
|
-
"react-native-builder-bob": "^0.40.
|
|
90
|
+
"react-native-builder-bob": "^0.40.8",
|
|
85
91
|
"react-native-nitro-modules": "*",
|
|
86
|
-
"release-it": "^
|
|
87
|
-
"turbo": "^1.
|
|
92
|
+
"release-it": "^17.10.0",
|
|
93
|
+
"turbo": "^1.10.7",
|
|
88
94
|
"typescript": "^5.9.2"
|
|
89
95
|
},
|
|
90
96
|
"peerDependencies": {
|
package/src/CallManager.nitro.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { type HybridObject, NitroModules } from 'react-native-nitro-modules';
|
|
3
3
|
import type { CallEventType } from './CallEventType';
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
// This is workaround for a swift compiler bug, its preventing us from using string[].
|
|
7
6
|
export interface StringHolder {
|
|
8
7
|
value: string;
|
|
@@ -14,7 +13,8 @@ export interface AudioRoutesInfo {
|
|
|
14
13
|
currentRoute: string; // Currently active audio route (e.g., "Speaker", "Earpiece", "Bluetooth", "Headset", "Unknown")
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
export interface CallManager
|
|
16
|
+
export interface CallManager
|
|
17
|
+
extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
|
|
18
18
|
// Call control
|
|
19
19
|
endCall(callId: string): void;
|
|
20
20
|
silenceRingtone(): void;
|
|
@@ -26,14 +26,29 @@ export interface CallManager extends HybridObject<{ ios: 'swift'; android: 'kotl
|
|
|
26
26
|
keepScreenAwake(keepAwake: boolean): void;
|
|
27
27
|
|
|
28
28
|
endAllCalls(): void;
|
|
29
|
-
startOutgoingCall(
|
|
30
|
-
|
|
29
|
+
startOutgoingCall(
|
|
30
|
+
callId: string,
|
|
31
|
+
callType: string,
|
|
32
|
+
targetName: string,
|
|
33
|
+
metadata?: string
|
|
34
|
+
): void;
|
|
35
|
+
startCall(
|
|
36
|
+
callId: string,
|
|
37
|
+
callType: string,
|
|
38
|
+
targetName: string,
|
|
39
|
+
metadata?: string
|
|
40
|
+
): void;
|
|
31
41
|
setOnHold(callId: string, onHold: boolean): void;
|
|
32
42
|
setMuted(callId: string, muted: boolean): void;
|
|
33
43
|
updateDisplayCallInformation(callId: string, callerName: string): void;
|
|
34
44
|
|
|
35
45
|
// added just new
|
|
36
|
-
reportIncomingCall(
|
|
46
|
+
reportIncomingCall(
|
|
47
|
+
callId: string,
|
|
48
|
+
callType: string,
|
|
49
|
+
targetName: string,
|
|
50
|
+
metadata?: string
|
|
51
|
+
): void;
|
|
37
52
|
|
|
38
53
|
// Event emitter: addListener returns a remove function
|
|
39
54
|
addListener(
|
|
@@ -46,10 +61,8 @@ export interface CallManager extends HybridObject<{ ios: 'swift'; android: 'kotl
|
|
|
46
61
|
listener: (payload: string) => void
|
|
47
62
|
): () => void;
|
|
48
63
|
|
|
49
|
-
|
|
50
|
-
hasActiveCall() :boolean; // if there is an active call, no matter ringing, incoming, outgoing, whatever
|
|
64
|
+
hasActiveCall(): boolean; // if there is an active call, no matter ringing, incoming, outgoing, whatever
|
|
51
65
|
}
|
|
52
66
|
|
|
53
|
-
|
|
54
67
|
export const CallManagerHybridObject =
|
|
55
68
|
NitroModules.createHybridObject<CallManager>('CallManager');
|