@qusaieilouti99/call-manager 0.1.67 → 0.1.69
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.
|
@@ -13,7 +13,6 @@ 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
|
|
17
16
|
import android.media.AudioManager
|
|
18
17
|
import android.media.MediaPlayer
|
|
19
18
|
import android.media.RingtoneManager
|
|
@@ -31,7 +30,6 @@ import android.telecom.PhoneAccountHandle
|
|
|
31
30
|
import android.telecom.TelecomManager
|
|
32
31
|
import android.telecom.VideoProfile
|
|
33
32
|
import android.util.Log
|
|
34
|
-
import androidx.annotation.RequiresApi
|
|
35
33
|
import kotlinx.coroutines.CoroutineScope
|
|
36
34
|
import kotlinx.coroutines.Dispatchers
|
|
37
35
|
import kotlinx.coroutines.launch
|
|
@@ -52,16 +50,11 @@ object CallEngine {
|
|
|
52
50
|
private val isInitialized = AtomicBoolean(false)
|
|
53
51
|
private val initializationLock = Any()
|
|
54
52
|
|
|
55
|
-
//
|
|
53
|
+
// Simplified Audio & Media Management (NO MANUAL AUDIO FOCUS)
|
|
56
54
|
private var ringtone: android.media.Ringtone? = null
|
|
57
55
|
private var ringbackPlayer: MediaPlayer? = null
|
|
58
56
|
private var audioManager: AudioManager? = null
|
|
59
57
|
private var wakeLock: PowerManager.WakeLock? = null
|
|
60
|
-
private var audioFocusRequest: AudioFocusRequest? = null
|
|
61
|
-
private var hasAudioFocus: Boolean = false
|
|
62
|
-
private val audioFocusRetryHandler = Handler(Looper.getMainLooper())
|
|
63
|
-
private var audioFocusRetryCount = 0
|
|
64
|
-
private val MAX_AUDIO_FOCUS_RETRIES = 3
|
|
65
58
|
|
|
66
59
|
// Call State Management
|
|
67
60
|
private val activeCalls = ConcurrentHashMap<String, CallInfo>()
|
|
@@ -86,38 +79,6 @@ object CallEngine {
|
|
|
86
79
|
fun onLockScreenBypassChanged(shouldBypass: Boolean)
|
|
87
80
|
}
|
|
88
81
|
|
|
89
|
-
// Enhanced Audio Focus Change Listener for Self-Managed Calls
|
|
90
|
-
private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
|
|
91
|
-
Log.d(TAG, "Audio focus changed: $focusChange")
|
|
92
|
-
when (focusChange) {
|
|
93
|
-
AudioManager.AUDIOFOCUS_GAIN -> {
|
|
94
|
-
Log.d(TAG, "Audio focus gained")
|
|
95
|
-
hasAudioFocus = true
|
|
96
|
-
audioFocusRetryCount = 0
|
|
97
|
-
|
|
98
|
-
// Resume any system-held calls after a short delay
|
|
99
|
-
Handler(Looper.getMainLooper()).postDelayed({
|
|
100
|
-
resumeSystemHeldCalls()
|
|
101
|
-
}, 500)
|
|
102
|
-
}
|
|
103
|
-
AudioManager.AUDIOFOCUS_LOSS -> {
|
|
104
|
-
Log.d(TAG, "Permanent audio focus loss - holding active calls")
|
|
105
|
-
hasAudioFocus = false
|
|
106
|
-
holdAllActiveCalls(heldBySystem = true)
|
|
107
|
-
}
|
|
108
|
-
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
|
|
109
|
-
Log.d(TAG, "Transient audio focus loss - holding calls temporarily")
|
|
110
|
-
hasAudioFocus = false
|
|
111
|
-
holdAllActiveCalls(heldBySystem = true)
|
|
112
|
-
}
|
|
113
|
-
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
|
|
114
|
-
Log.d(TAG, "Transient audio focus loss (can duck) - keeping calls active")
|
|
115
|
-
hasAudioFocus = false
|
|
116
|
-
// Don't hold calls for ducking scenarios in self-managed calls
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
82
|
// --- INITIALIZATION ---
|
|
122
83
|
fun initialize(context: Context) {
|
|
123
84
|
synchronized(initializationLock) {
|
|
@@ -165,102 +126,6 @@ object CallEngine {
|
|
|
165
126
|
}
|
|
166
127
|
}
|
|
167
128
|
|
|
168
|
-
// --- Enhanced Audio Focus Management for Self-Managed Calls ---
|
|
169
|
-
private fun requestAudioFocus(): Boolean {
|
|
170
|
-
val context = requireContext()
|
|
171
|
-
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
|
|
172
|
-
|
|
173
|
-
if (hasAudioFocus) {
|
|
174
|
-
Log.d(TAG, "Audio focus already granted")
|
|
175
|
-
return true
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
Log.d(TAG, "Requesting audio focus for self-managed call (attempt ${audioFocusRetryCount + 1})")
|
|
179
|
-
|
|
180
|
-
val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
181
|
-
requestAudioFocusApi26Plus()
|
|
182
|
-
} else {
|
|
183
|
-
requestAudioFocusLegacy()
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
val success = result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
|
|
187
|
-
hasAudioFocus = success
|
|
188
|
-
|
|
189
|
-
Log.d(TAG, "Audio focus request result: $result (granted: $success)")
|
|
190
|
-
|
|
191
|
-
if (!success && audioFocusRetryCount < MAX_AUDIO_FOCUS_RETRIES) {
|
|
192
|
-
// Retry after a short delay
|
|
193
|
-
audioFocusRetryCount++
|
|
194
|
-
audioFocusRetryHandler.postDelayed({
|
|
195
|
-
Log.d(TAG, "Retrying audio focus request...")
|
|
196
|
-
requestAudioFocus()
|
|
197
|
-
}, 200)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return success
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
@RequiresApi(Build.VERSION_CODES.O)
|
|
204
|
-
private fun requestAudioFocusApi26Plus(): Int {
|
|
205
|
-
if (audioFocusRequest == null) {
|
|
206
|
-
audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
|
|
207
|
-
.setAudioAttributes(
|
|
208
|
-
AudioAttributes.Builder()
|
|
209
|
-
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
|
|
210
|
-
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
|
211
|
-
.setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
|
|
212
|
-
.build()
|
|
213
|
-
)
|
|
214
|
-
.setOnAudioFocusChangeListener(audioFocusChangeListener)
|
|
215
|
-
.setAcceptsDelayedFocusGain(true)
|
|
216
|
-
.setWillPauseWhenDucked(false)
|
|
217
|
-
.build()
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return audioManager?.requestAudioFocus(audioFocusRequest!!) ?: AudioManager.AUDIOFOCUS_REQUEST_FAILED
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
@Suppress("DEPRECATION")
|
|
224
|
-
private fun requestAudioFocusLegacy(): Int {
|
|
225
|
-
return audioManager?.requestAudioFocus(
|
|
226
|
-
audioFocusChangeListener,
|
|
227
|
-
AudioManager.STREAM_VOICE_CALL,
|
|
228
|
-
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
|
|
229
|
-
) ?: AudioManager.AUDIOFOCUS_REQUEST_FAILED
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
private fun abandonAudioFocus() {
|
|
233
|
-
if (!hasAudioFocus) return
|
|
234
|
-
|
|
235
|
-
audioManager?.let { am ->
|
|
236
|
-
val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
237
|
-
audioFocusRequest?.let { request ->
|
|
238
|
-
am.abandonAudioFocusRequest(request)
|
|
239
|
-
} ?: AudioManager.AUDIOFOCUS_REQUEST_FAILED
|
|
240
|
-
} else {
|
|
241
|
-
@Suppress("DEPRECATION")
|
|
242
|
-
am.abandonAudioFocus(audioFocusChangeListener)
|
|
243
|
-
}
|
|
244
|
-
Log.d(TAG, "Audio focus abandoned, result: $result")
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
hasAudioFocus = false
|
|
248
|
-
audioFocusRetryCount = 0
|
|
249
|
-
audioFocusRetryHandler.removeCallbacksAndMessages(null)
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
private fun holdAllActiveCalls(heldBySystem: Boolean) {
|
|
253
|
-
activeCalls.values.filter { it.state == CallState.ACTIVE }.forEach { call ->
|
|
254
|
-
holdCallInternal(call.callId, heldBySystem = heldBySystem)
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
private fun resumeSystemHeldCalls() {
|
|
259
|
-
activeCalls.values.filter { it.state == CallState.HELD && it.wasHeldBySystem }.forEach { call ->
|
|
260
|
-
unholdCallInternal(call.callId, resumedBySystem = true)
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
129
|
// --- Lock Screen Bypass Management ---
|
|
265
130
|
fun registerLockScreenBypassCallback(callback: LockScreenBypassCallback) {
|
|
266
131
|
lockScreenBypassCallbacks.add(callback)
|
|
@@ -387,7 +252,7 @@ object CallEngine {
|
|
|
387
252
|
updateLockScreenBypass()
|
|
388
253
|
}
|
|
389
254
|
|
|
390
|
-
// ---
|
|
255
|
+
// --- Outgoing Call Management ---
|
|
391
256
|
fun startOutgoingCall(
|
|
392
257
|
callId: String,
|
|
393
258
|
callType: String,
|
|
@@ -419,10 +284,8 @@ object CallEngine {
|
|
|
419
284
|
currentCallId = callId
|
|
420
285
|
Log.d(TAG, "Call $callId added to activeCalls. State: DIALING")
|
|
421
286
|
|
|
422
|
-
//
|
|
287
|
+
// ONLY set audio mode - let system handle audio focus for self-managed calls
|
|
423
288
|
setAudioMode()
|
|
424
|
-
val audioFocusGranted = requestAudioFocus()
|
|
425
|
-
Log.d(TAG, "Audio focus for outgoing call: $audioFocusGranted")
|
|
426
289
|
|
|
427
290
|
registerPhoneAccount()
|
|
428
291
|
val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
|
|
@@ -447,10 +310,8 @@ object CallEngine {
|
|
|
447
310
|
telecomManager.placeCall(addressUri, extras)
|
|
448
311
|
startForegroundService()
|
|
449
312
|
|
|
450
|
-
// Start ringback
|
|
451
|
-
|
|
452
|
-
startRingback()
|
|
453
|
-
}
|
|
313
|
+
// Start ringback (system will handle audio focus)
|
|
314
|
+
startRingback()
|
|
454
315
|
|
|
455
316
|
bringAppToForeground()
|
|
456
317
|
keepScreenAwake(true)
|
|
@@ -494,7 +355,6 @@ object CallEngine {
|
|
|
494
355
|
|
|
495
356
|
registerPhoneAccount()
|
|
496
357
|
setAudioMode()
|
|
497
|
-
requestAudioFocus()
|
|
498
358
|
bringAppToForeground()
|
|
499
359
|
startForegroundService()
|
|
500
360
|
keepScreenAwake(true)
|
|
@@ -505,7 +365,7 @@ object CallEngine {
|
|
|
505
365
|
emitOutgoingCallAnsweredWithMetadata(callId)
|
|
506
366
|
}
|
|
507
367
|
|
|
508
|
-
// ---
|
|
368
|
+
// --- Call Answer Management (SIMPLIFIED - NO MANUAL AUDIO FOCUS) ---
|
|
509
369
|
fun callAnsweredFromJS(callId: String) {
|
|
510
370
|
Log.d(TAG, "callAnsweredFromJS: $callId - remote party answered")
|
|
511
371
|
coreCallAnswered(callId, isLocalAnswer = false)
|
|
@@ -516,7 +376,7 @@ object CallEngine {
|
|
|
516
376
|
coreCallAnswered(callId, isLocalAnswer = true)
|
|
517
377
|
}
|
|
518
378
|
|
|
519
|
-
//
|
|
379
|
+
// SIMPLIFIED: Let system handle audio focus for self-managed calls
|
|
520
380
|
private fun coreCallAnswered(callId: String, isLocalAnswer: Boolean) {
|
|
521
381
|
Log.d(TAG, "coreCallAnswered: $callId, isLocalAnswer: $isLocalAnswer")
|
|
522
382
|
|
|
@@ -526,19 +386,13 @@ object CallEngine {
|
|
|
526
386
|
return
|
|
527
387
|
}
|
|
528
388
|
|
|
529
|
-
// Set audio mode
|
|
389
|
+
// Set audio mode and let system handle audio focus
|
|
530
390
|
setAudioMode()
|
|
531
391
|
|
|
532
|
-
//
|
|
533
|
-
val audioFocusGranted = requestAudioFocus()
|
|
534
|
-
if (!audioFocusGranted) {
|
|
535
|
-
Log.w(TAG, "Audio focus not granted for call $callId, but proceeding anyway")
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// Now set call to ACTIVE
|
|
392
|
+
// Set call to ACTIVE
|
|
539
393
|
activeCalls[callId] = callInfo.copy(state = CallState.ACTIVE)
|
|
540
394
|
currentCallId = callId
|
|
541
|
-
Log.d(TAG, "Call $callId set to ACTIVE state (audio focus
|
|
395
|
+
Log.d(TAG, "Call $callId set to ACTIVE state (system manages audio focus)")
|
|
542
396
|
|
|
543
397
|
// Clean up media and UI
|
|
544
398
|
stopRingtone()
|
|
@@ -660,11 +514,6 @@ object CallEngine {
|
|
|
660
514
|
return
|
|
661
515
|
}
|
|
662
516
|
|
|
663
|
-
// Request audio focus when resuming a call
|
|
664
|
-
if (resumedBySystem) {
|
|
665
|
-
requestAudioFocus()
|
|
666
|
-
}
|
|
667
|
-
|
|
668
517
|
activeCalls[callId] = callInfo.copy(
|
|
669
518
|
state = CallState.ACTIVE,
|
|
670
519
|
wasHeldBySystem = false
|
|
@@ -780,7 +629,7 @@ object CallEngine {
|
|
|
780
629
|
})
|
|
781
630
|
}
|
|
782
631
|
|
|
783
|
-
// --- Audio Management ---
|
|
632
|
+
// --- Enhanced Audio Management ---
|
|
784
633
|
fun getAudioDevices(): AudioRoutesInfo {
|
|
785
634
|
val context = requireContext()
|
|
786
635
|
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager ?: run {
|
|
@@ -788,8 +637,12 @@ object CallEngine {
|
|
|
788
637
|
}
|
|
789
638
|
|
|
790
639
|
val devices = mutableSetOf<String>()
|
|
791
|
-
var currentRoute = "Earpiece"
|
|
792
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
|
|
793
646
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
794
647
|
val audioDeviceInfoList = audioManager?.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
|
|
795
648
|
audioDeviceInfoList?.forEach { device ->
|
|
@@ -800,24 +653,21 @@ object CallEngine {
|
|
|
800
653
|
AudioDeviceInfo.TYPE_WIRED_HEADPHONES, AudioDeviceInfo.TYPE_WIRED_HEADSET -> {
|
|
801
654
|
devices.add("Headset")
|
|
802
655
|
}
|
|
803
|
-
|
|
804
|
-
devices.add("Speaker")
|
|
805
|
-
}
|
|
806
|
-
AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> {
|
|
807
|
-
devices.add("Earpiece")
|
|
808
|
-
}
|
|
656
|
+
// Speaker and Earpiece already added above
|
|
809
657
|
}
|
|
810
658
|
}
|
|
811
659
|
} else {
|
|
812
|
-
|
|
660
|
+
// For older versions, check for Bluetooth and Headset
|
|
661
|
+
if (audioManager?.isBluetoothA2dpOn == true || audioManager?.isBluetoothScoOn == true) {
|
|
662
|
+
devices.add("Bluetooth")
|
|
663
|
+
}
|
|
664
|
+
if (audioManager?.isWiredHeadsetOn == true) {
|
|
665
|
+
devices.add("Headset")
|
|
666
|
+
}
|
|
813
667
|
}
|
|
814
668
|
|
|
815
|
-
currentRoute =
|
|
816
|
-
|
|
817
|
-
audioManager?.isSpeakerphoneOn == true -> "Speaker"
|
|
818
|
-
audioManager?.isWiredHeadsetOn == true -> "Headset"
|
|
819
|
-
else -> "Earpiece"
|
|
820
|
-
}
|
|
669
|
+
val currentRoute = getCurrentAudioRoute()
|
|
670
|
+
Log.d(TAG, "Available audio devices: ${devices.toList()}, current route: $currentRoute")
|
|
821
671
|
|
|
822
672
|
return AudioRoutesInfo(devices.toTypedArray(), currentRoute)
|
|
823
673
|
}
|
|
@@ -829,6 +679,7 @@ object CallEngine {
|
|
|
829
679
|
|
|
830
680
|
val previousRoute = getCurrentAudioRoute()
|
|
831
681
|
|
|
682
|
+
// Reset all routes first
|
|
832
683
|
audioManager?.isSpeakerphoneOn = false
|
|
833
684
|
audioManager?.stopBluetoothSco()
|
|
834
685
|
audioManager?.isBluetoothScoOn = false
|
|
@@ -840,6 +691,7 @@ object CallEngine {
|
|
|
840
691
|
}
|
|
841
692
|
"Earpiece" -> {
|
|
842
693
|
audioManager?.mode = AudioManager.MODE_IN_COMMUNICATION
|
|
694
|
+
// Earpiece is the default - just ensure speaker and bluetooth are off
|
|
843
695
|
}
|
|
844
696
|
"Bluetooth" -> {
|
|
845
697
|
audioManager?.startBluetoothSco()
|
|
@@ -848,6 +700,7 @@ object CallEngine {
|
|
|
848
700
|
}
|
|
849
701
|
"Headset" -> {
|
|
850
702
|
audioManager?.mode = AudioManager.MODE_IN_COMMUNICATION
|
|
703
|
+
// Headset routing is automatic when connected
|
|
851
704
|
}
|
|
852
705
|
else -> {
|
|
853
706
|
Log.w(TAG, "Unknown audio route: $route")
|
|
@@ -855,12 +708,30 @@ object CallEngine {
|
|
|
855
708
|
}
|
|
856
709
|
}
|
|
857
710
|
|
|
711
|
+
val newRoute = getCurrentAudioRoute()
|
|
712
|
+
if (previousRoute != newRoute) {
|
|
713
|
+
// Emit unified event with full context
|
|
714
|
+
emitAudioRouteChanged()
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
858
718
|
val newRoute = getCurrentAudioRoute()
|
|
859
719
|
if (previousRoute != newRoute) {
|
|
860
720
|
emitEvent(CallEventType.AUDIO_ROUTE_CHANGED, JSONObject().put("route", newRoute))
|
|
861
721
|
}
|
|
862
722
|
}
|
|
863
723
|
|
|
724
|
+
// UNIFIED event emission - always sends full audio context
|
|
725
|
+
private fun emitAudioRouteChanged() {
|
|
726
|
+
val audioInfo = getAudioDevices()
|
|
727
|
+
val jsonPayload = JSONObject().apply {
|
|
728
|
+
put("devices", JSONArray(audioInfo.devices.toList()))
|
|
729
|
+
put("currentRoute", audioInfo.currentRoute)
|
|
730
|
+
}
|
|
731
|
+
emitEvent(CallEventType.AUDIO_ROUTE_CHANGED, jsonPayload)
|
|
732
|
+
Log.d(TAG, "Audio route changed: ${audioInfo.currentRoute}, available: ${audioInfo.devices.toList()}")
|
|
733
|
+
}
|
|
734
|
+
|
|
864
735
|
private fun getCurrentAudioRoute(): String {
|
|
865
736
|
return when {
|
|
866
737
|
audioManager?.isBluetoothScoOn == true -> "Bluetooth"
|
|
@@ -886,6 +757,7 @@ object CallEngine {
|
|
|
886
757
|
|
|
887
758
|
private fun setAudioMode() {
|
|
888
759
|
audioManager?.mode = AudioManager.MODE_IN_COMMUNICATION
|
|
760
|
+
Log.d(TAG, "Audio mode set to MODE_IN_COMMUNICATION (system handles audio focus)")
|
|
889
761
|
}
|
|
890
762
|
|
|
891
763
|
private fun resetAudioMode() {
|
|
@@ -894,21 +766,34 @@ object CallEngine {
|
|
|
894
766
|
audioManager?.stopBluetoothSco()
|
|
895
767
|
audioManager?.isBluetoothScoOn = false
|
|
896
768
|
audioManager?.isSpeakerphoneOn = false
|
|
897
|
-
|
|
769
|
+
Log.d(TAG, "Audio mode reset to MODE_NORMAL")
|
|
898
770
|
}
|
|
899
771
|
}
|
|
900
772
|
|
|
901
|
-
// --- Audio Device Callback ---
|
|
773
|
+
// --- Audio Device Callback (simplified) ---
|
|
902
774
|
private val audioDeviceCallback = object : AudioDeviceCallback() {
|
|
903
775
|
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
|
|
904
|
-
|
|
776
|
+
Log.d(TAG, "Audio devices added")
|
|
777
|
+
emitAudioDevicesChanged()
|
|
905
778
|
}
|
|
906
779
|
|
|
907
780
|
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>?) {
|
|
908
|
-
|
|
781
|
+
Log.d(TAG, "Audio devices removed")
|
|
782
|
+
emitAudioDevicesChanged()
|
|
909
783
|
}
|
|
910
784
|
}
|
|
911
785
|
|
|
786
|
+
// Separate event for when physical devices are added/removed
|
|
787
|
+
private fun emitAudioDevicesChanged() {
|
|
788
|
+
val audioInfo = getAudioDevices()
|
|
789
|
+
val jsonPayload = JSONObject().apply {
|
|
790
|
+
put("devices", JSONArray(audioInfo.devices.toList()))
|
|
791
|
+
put("currentRoute", audioInfo.currentRoute)
|
|
792
|
+
}
|
|
793
|
+
emitEvent(CallEventType.AUDIO_DEVICES_CHANGED, jsonPayload)
|
|
794
|
+
Log.d(TAG, "Audio devices changed: available: ${audioInfo.devices.toList()}")
|
|
795
|
+
}
|
|
796
|
+
|
|
912
797
|
fun registerAudioDeviceCallback() {
|
|
913
798
|
val context = requireContext()
|
|
914
799
|
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
@@ -921,22 +806,6 @@ object CallEngine {
|
|
|
921
806
|
audioManager?.unregisterAudioDeviceCallback(audioDeviceCallback)
|
|
922
807
|
}
|
|
923
808
|
|
|
924
|
-
private fun emitAudioDevicesChangedIfNeeded() {
|
|
925
|
-
val currentAudioInfo = getAudioDevices()
|
|
926
|
-
|
|
927
|
-
if (lastAudioRoutesInfo == null ||
|
|
928
|
-
!currentAudioInfo.devices.contentEquals(lastAudioRoutesInfo!!.devices) ||
|
|
929
|
-
currentAudioInfo.currentRoute != lastAudioRoutesInfo!!.currentRoute) {
|
|
930
|
-
|
|
931
|
-
lastAudioRoutesInfo = currentAudioInfo
|
|
932
|
-
val jsonPayload = JSONObject().apply {
|
|
933
|
-
put("devices", JSONArray(currentAudioInfo.devices.toList()))
|
|
934
|
-
put("currentRoute", currentAudioInfo.currentRoute)
|
|
935
|
-
}
|
|
936
|
-
emitEvent(CallEventType.AUDIO_DEVICES_CHANGED, jsonPayload)
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
|
|
940
809
|
// --- Screen Management ---
|
|
941
810
|
fun keepScreenAwake(keepAwake: Boolean) {
|
|
942
811
|
val context = requireContext()
|
|
@@ -84,19 +84,11 @@ class MyConnection(
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
// Only emit route change if route actually changed
|
|
87
88
|
if (lastAudioState == null || lastAudioState!!.route != state.route) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
)
|
|
89
|
+
// Don't emit here - let CallEngine handle it to avoid duplication
|
|
90
|
+
// The system audio state change will be detected by CallEngine's audio management
|
|
91
|
+
Log.d(TAG, "System audio route changed to: ${state.route} for callId: $callId")
|
|
100
92
|
}
|
|
101
93
|
|
|
102
94
|
lastAudioState = state
|