@qusaieilouti99/call-manager 0.1.68 → 0.1.70
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.
|
@@ -30,9 +30,6 @@ import android.telecom.PhoneAccountHandle
|
|
|
30
30
|
import android.telecom.TelecomManager
|
|
31
31
|
import android.telecom.VideoProfile
|
|
32
32
|
import android.util.Log
|
|
33
|
-
import kotlinx.coroutines.CoroutineScope
|
|
34
|
-
import kotlinx.coroutines.Dispatchers
|
|
35
|
-
import kotlinx.coroutines.launch
|
|
36
33
|
import org.json.JSONArray
|
|
37
34
|
import org.json.JSONObject
|
|
38
35
|
import java.util.concurrent.ConcurrentHashMap
|
|
@@ -275,8 +272,11 @@ object CallEngine {
|
|
|
275
272
|
|
|
276
273
|
val isVideoCall = callType == "Video"
|
|
277
274
|
if (!canMakeMultipleCalls && activeCalls.isNotEmpty()) {
|
|
278
|
-
activeCalls.values.
|
|
279
|
-
|
|
275
|
+
activeCalls.values.forEach {
|
|
276
|
+
if (it.state == CallState.ACTIVE) {
|
|
277
|
+
holdCallInternal(it.callId, heldBySystem = false)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
// Track dialing state
|
|
@@ -629,7 +629,7 @@ object CallEngine {
|
|
|
629
629
|
})
|
|
630
630
|
}
|
|
631
631
|
|
|
632
|
-
// --- Audio Management
|
|
632
|
+
// --- Enhanced Audio Management ---
|
|
633
633
|
fun getAudioDevices(): AudioRoutesInfo {
|
|
634
634
|
val context = requireContext()
|
|
635
635
|
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager ?: run {
|
|
@@ -637,8 +637,12 @@ object CallEngine {
|
|
|
637
637
|
}
|
|
638
638
|
|
|
639
639
|
val devices = mutableSetOf<String>()
|
|
640
|
-
var currentRoute = "Earpiece"
|
|
641
640
|
|
|
641
|
+
// ALWAYS include Speaker and Earpiece for phone calls
|
|
642
|
+
devices.add("Speaker")
|
|
643
|
+
devices.add("Earpiece")
|
|
644
|
+
|
|
645
|
+
// Check for additional connected devices
|
|
642
646
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
643
647
|
val audioDeviceInfoList = audioManager?.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
|
|
644
648
|
audioDeviceInfoList?.forEach { device ->
|
|
@@ -649,24 +653,26 @@ object CallEngine {
|
|
|
649
653
|
AudioDeviceInfo.TYPE_WIRED_HEADPHONES, AudioDeviceInfo.TYPE_WIRED_HEADSET -> {
|
|
650
654
|
devices.add("Headset")
|
|
651
655
|
}
|
|
652
|
-
|
|
653
|
-
devices.add("Speaker")
|
|
654
|
-
}
|
|
655
|
-
AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> {
|
|
656
|
-
devices.add("Earpiece")
|
|
657
|
-
}
|
|
656
|
+
// Speaker and Earpiece already added above if they are reported, but explicit add handles cases where they are not
|
|
658
657
|
}
|
|
659
658
|
}
|
|
660
659
|
} else {
|
|
661
|
-
|
|
660
|
+
// For older versions, check for Bluetooth and Headset
|
|
661
|
+
@Suppress("DEPRECATION")
|
|
662
|
+
if (audioManager?.isBluetoothA2dpOn == true || audioManager?.isBluetoothScoOn == true) {
|
|
663
|
+
devices.add("Bluetooth")
|
|
664
|
+
}
|
|
665
|
+
@Suppress("DEPRECATION")
|
|
666
|
+
if (audioManager?.isWiredHeadsetOn == true) {
|
|
667
|
+
devices.add("Headset")
|
|
668
|
+
}
|
|
662
669
|
}
|
|
663
670
|
|
|
664
|
-
currentRoute =
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
}
|
|
671
|
+
val currentRoute = getCurrentAudioRoute()
|
|
672
|
+
Log.d(TAG, "Available audio devices: ${devices.toList()}, current route: $currentRoute")
|
|
673
|
+
|
|
674
|
+
// Update last known audio routes info
|
|
675
|
+
lastAudioRoutesInfo = AudioRoutesInfo(devices.toTypedArray(), currentRoute)
|
|
670
676
|
|
|
671
677
|
return AudioRoutesInfo(devices.toTypedArray(), currentRoute)
|
|
672
678
|
}
|
|
@@ -678,6 +684,7 @@ object CallEngine {
|
|
|
678
684
|
|
|
679
685
|
val previousRoute = getCurrentAudioRoute()
|
|
680
686
|
|
|
687
|
+
// Reset all routes first
|
|
681
688
|
audioManager?.isSpeakerphoneOn = false
|
|
682
689
|
audioManager?.stopBluetoothSco()
|
|
683
690
|
audioManager?.isBluetoothScoOn = false
|
|
@@ -689,6 +696,7 @@ object CallEngine {
|
|
|
689
696
|
}
|
|
690
697
|
"Earpiece" -> {
|
|
691
698
|
audioManager?.mode = AudioManager.MODE_IN_COMMUNICATION
|
|
699
|
+
// Earpiece is the default - just ensure speaker and bluetooth are off
|
|
692
700
|
}
|
|
693
701
|
"Bluetooth" -> {
|
|
694
702
|
audioManager?.startBluetoothSco()
|
|
@@ -697,6 +705,7 @@ object CallEngine {
|
|
|
697
705
|
}
|
|
698
706
|
"Headset" -> {
|
|
699
707
|
audioManager?.mode = AudioManager.MODE_IN_COMMUNICATION
|
|
708
|
+
// Headset routing is automatic when connected
|
|
700
709
|
}
|
|
701
710
|
else -> {
|
|
702
711
|
Log.w(TAG, "Unknown audio route: $route")
|
|
@@ -706,7 +715,8 @@ object CallEngine {
|
|
|
706
715
|
|
|
707
716
|
val newRoute = getCurrentAudioRoute()
|
|
708
717
|
if (previousRoute != newRoute) {
|
|
709
|
-
|
|
718
|
+
// Emit unified event with full context
|
|
719
|
+
emitAudioRouteChanged()
|
|
710
720
|
}
|
|
711
721
|
}
|
|
712
722
|
|
|
@@ -720,7 +730,7 @@ object CallEngine {
|
|
|
720
730
|
}
|
|
721
731
|
|
|
722
732
|
private fun setInitialAudioRoute(callType: String) {
|
|
723
|
-
val availableDevices = getAudioDevices()
|
|
733
|
+
val availableDevices = getAudioDevices() // This will update lastAudioRoutesInfo
|
|
724
734
|
|
|
725
735
|
val defaultRoute = when {
|
|
726
736
|
availableDevices.devices.contains("Bluetooth") -> "Bluetooth"
|
|
@@ -748,17 +758,44 @@ object CallEngine {
|
|
|
748
758
|
}
|
|
749
759
|
}
|
|
750
760
|
|
|
761
|
+
// UNIFIED event emission - always sends full audio context
|
|
762
|
+
private fun emitAudioRouteChanged() {
|
|
763
|
+
// Re-calculate latest state to ensure accuracy
|
|
764
|
+
val audioInfo = getAudioDevices() // This updates lastAudioRoutesInfo internally
|
|
765
|
+
val jsonPayload = JSONObject().apply {
|
|
766
|
+
put("devices", JSONArray(audioInfo.devices.toList()))
|
|
767
|
+
put("currentRoute", audioInfo.currentRoute)
|
|
768
|
+
}
|
|
769
|
+
emitEvent(CallEventType.AUDIO_ROUTE_CHANGED, jsonPayload)
|
|
770
|
+
Log.d(TAG, "Audio route changed: ${audioInfo.currentRoute}, available: ${audioInfo.devices.toList()}")
|
|
771
|
+
}
|
|
772
|
+
|
|
751
773
|
// --- Audio Device Callback ---
|
|
752
774
|
private val audioDeviceCallback = object : AudioDeviceCallback() {
|
|
753
775
|
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
|
|
754
|
-
|
|
776
|
+
Log.d(TAG, "Audio devices added")
|
|
777
|
+
emitAudioDevicesChanged()
|
|
755
778
|
}
|
|
756
779
|
|
|
757
780
|
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>?) {
|
|
758
|
-
|
|
781
|
+
Log.d(TAG, "Audio devices removed")
|
|
782
|
+
emitAudioDevicesChanged()
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Event for when physical devices are added/removed - includes full audio context
|
|
787
|
+
private fun emitAudioDevicesChanged() {
|
|
788
|
+
// Re-calculate latest state to ensure accuracy
|
|
789
|
+
val audioInfo = getAudioDevices() // This updates lastAudioRoutesInfo internally
|
|
790
|
+
val jsonPayload = JSONObject().apply {
|
|
791
|
+
put("devices", JSONArray(audioInfo.devices.toList()))
|
|
792
|
+
put("currentRoute", audioInfo.currentRoute)
|
|
759
793
|
}
|
|
794
|
+
emitEvent(CallEventType.AUDIO_DEVICES_CHANGED, jsonPayload)
|
|
795
|
+
Log.d(TAG, "Audio devices changed: available: ${audioInfo.devices.toList()}")
|
|
760
796
|
}
|
|
761
797
|
|
|
798
|
+
|
|
762
799
|
fun registerAudioDeviceCallback() {
|
|
763
800
|
val context = requireContext()
|
|
764
801
|
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
@@ -771,22 +808,6 @@ object CallEngine {
|
|
|
771
808
|
audioManager?.unregisterAudioDeviceCallback(audioDeviceCallback)
|
|
772
809
|
}
|
|
773
810
|
|
|
774
|
-
private fun emitAudioDevicesChangedIfNeeded() {
|
|
775
|
-
val currentAudioInfo = getAudioDevices()
|
|
776
|
-
|
|
777
|
-
if (lastAudioRoutesInfo == null ||
|
|
778
|
-
!currentAudioInfo.devices.contentEquals(lastAudioRoutesInfo!!.devices) ||
|
|
779
|
-
currentAudioInfo.currentRoute != lastAudioRoutesInfo!!.currentRoute) {
|
|
780
|
-
|
|
781
|
-
lastAudioRoutesInfo = currentAudioInfo
|
|
782
|
-
val jsonPayload = JSONObject().apply {
|
|
783
|
-
put("devices", JSONArray(currentAudioInfo.devices.toList()))
|
|
784
|
-
put("currentRoute", currentAudioInfo.currentRoute)
|
|
785
|
-
}
|
|
786
|
-
emitEvent(CallEventType.AUDIO_DEVICES_CHANGED, jsonPayload)
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
|
|
790
811
|
// --- Screen Management ---
|
|
791
812
|
fun keepScreenAwake(keepAwake: Boolean) {
|
|
792
813
|
val context = requireContext()
|
|
@@ -25,8 +25,8 @@ class MyConnection(
|
|
|
25
25
|
init {
|
|
26
26
|
connectionProperties = Connection.PROPERTY_SELF_MANAGED
|
|
27
27
|
connectionCapabilities = Connection.CAPABILITY_SUPPORT_HOLD or
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
Connection.CAPABILITY_MUTE or
|
|
29
|
+
Connection.CAPABILITY_HOLD
|
|
30
30
|
|
|
31
31
|
if (callType == "Video") {
|
|
32
32
|
Log.d(TAG, "MyConnection for callId $callId initialized as VIDEO call.")
|
|
@@ -84,19 +84,11 @@ class MyConnection(
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
// Only react to route change if it's different.
|
|
88
|
+
// DO NOT emit AUDIO_ROUTE_CHANGED from here, let CallEngine's
|
|
89
|
+
// AudioDeviceCallback handle it for consistency and to avoid duplication.
|
|
87
90
|
if (lastAudioState == null || lastAudioState!!.route != state.route) {
|
|
88
|
-
|
|
89
|
-
CallAudioState.ROUTE_SPEAKER -> "Speaker"
|
|
90
|
-
CallAudioState.ROUTE_EARPIECE -> "Earpiece"
|
|
91
|
-
CallAudioState.ROUTE_BLUETOOTH -> "Bluetooth"
|
|
92
|
-
CallAudioState.ROUTE_WIRED_HEADSET -> "Headset"
|
|
93
|
-
else -> "Unknown"
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
CallEngine.emitEvent(
|
|
97
|
-
CallEventType.AUDIO_ROUTE_CHANGED,
|
|
98
|
-
JSONObject().put("callId", callId).put("route", routeName)
|
|
99
|
-
)
|
|
91
|
+
Log.d(TAG, "System audio route changed for callId: $callId. Telecom route: ${state.route}")
|
|
100
92
|
}
|
|
101
93
|
|
|
102
94
|
lastAudioState = state
|