@qusaieilouti99/call-manager 0.1.113 → 0.1.115
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.
|
@@ -42,8 +42,7 @@ import android.os.Vibrator
|
|
|
42
42
|
import android.os.VibrationEffect
|
|
43
43
|
import android.content.BroadcastReceiver
|
|
44
44
|
import android.content.IntentFilter
|
|
45
|
-
import android.
|
|
46
|
-
import android.provider.Settings
|
|
45
|
+
import android.view.KeyEvent
|
|
47
46
|
|
|
48
47
|
/**
|
|
49
48
|
* Core call‐management engine. Manages self-managed telecom calls,
|
|
@@ -55,16 +54,7 @@ object CallEngine {
|
|
|
55
54
|
private const val NOTIF_CHANNEL_ID = "incoming_call_channel"
|
|
56
55
|
private const val NOTIF_ID = 2001
|
|
57
56
|
|
|
58
|
-
// Audio routing timing constants
|
|
59
|
-
private const val AUDIO_ROUTE_DELAY_AFTER_ANSWER = 500L
|
|
60
|
-
private const val AUDIO_ROUTE_RETRY_DELAY = 200L
|
|
61
|
-
private const val MAX_AUDIO_ROUTE_RETRIES = 3
|
|
62
|
-
|
|
63
57
|
// --- NEW: Call-end listener API ---
|
|
64
|
-
/**
|
|
65
|
-
* Implement this in your UI (CallActivity). Will be called
|
|
66
|
-
* on the main thread whenever a call is ended.
|
|
67
|
-
*/
|
|
68
58
|
interface CallEndListener {
|
|
69
59
|
fun onCallEnded(callId: String)
|
|
70
60
|
}
|
|
@@ -82,30 +72,28 @@ object CallEngine {
|
|
|
82
72
|
fun unregisterCallEndListener(l: CallEndListener) {
|
|
83
73
|
callEndListeners.remove(l)
|
|
84
74
|
}
|
|
85
|
-
// --- end listener API ---
|
|
86
75
|
|
|
87
76
|
// Core context - initialized once and maintained
|
|
88
77
|
@Volatile private var appContext: Context? = null
|
|
89
78
|
private val isInitialized = AtomicBoolean(false)
|
|
90
79
|
private val initializationLock = Any()
|
|
91
80
|
|
|
92
|
-
// Audio & Media Management
|
|
81
|
+
// Simplified Audio & Media Management
|
|
93
82
|
private var ringtone: android.media.Ringtone? = null
|
|
94
83
|
private var ringbackPlayer: MediaPlayer? = null
|
|
95
84
|
|
|
96
|
-
//
|
|
85
|
+
// FIXED: New volume key handling approach
|
|
97
86
|
private var vibrator: Vibrator? = null
|
|
98
|
-
private var
|
|
99
|
-
private var
|
|
100
|
-
private var isMonitoringVolume = false
|
|
87
|
+
private var volumeKeyReceiver: BroadcastReceiver? = null
|
|
88
|
+
private var isRingtonePlaying = false
|
|
101
89
|
|
|
102
90
|
private var audioManager: AudioManager? = null
|
|
103
91
|
private var wakeLock: PowerManager.WakeLock? = null
|
|
104
92
|
|
|
105
|
-
//
|
|
106
|
-
private var
|
|
107
|
-
private var
|
|
108
|
-
private val
|
|
93
|
+
// FIXED: New audio routing approach
|
|
94
|
+
private var desiredAudioRoute: String? = null
|
|
95
|
+
private var isWaitingForTelecomAudioState = false
|
|
96
|
+
private val audioRoutingHandler = Handler(Looper.getMainLooper())
|
|
109
97
|
|
|
110
98
|
// Call State Management
|
|
111
99
|
private val activeCalls = ConcurrentHashMap<String, CallInfo>()
|
|
@@ -136,7 +124,7 @@ object CallEngine {
|
|
|
136
124
|
if (isInitialized.compareAndSet(false, true)) {
|
|
137
125
|
appContext = context.applicationContext
|
|
138
126
|
audioManager = appContext?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
|
|
139
|
-
|
|
127
|
+
setupVolumeKeyDetection()
|
|
140
128
|
Log.d(TAG, "CallEngine initialized successfully")
|
|
141
129
|
|
|
142
130
|
if (isCallActive()) {
|
|
@@ -154,61 +142,69 @@ object CallEngine {
|
|
|
154
142
|
)
|
|
155
143
|
}
|
|
156
144
|
|
|
157
|
-
// ---
|
|
158
|
-
private fun
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
145
|
+
// --- FIXED: Volume Key Detection for Ringtone Silencing ---
|
|
146
|
+
private fun setupVolumeKeyDetection() {
|
|
147
|
+
volumeKeyReceiver = object : BroadcastReceiver() {
|
|
148
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
149
|
+
when (intent.action) {
|
|
150
|
+
Intent.ACTION_MEDIA_BUTTON -> {
|
|
151
|
+
val keyEvent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
152
|
+
intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java)
|
|
153
|
+
} else {
|
|
154
|
+
@Suppress("DEPRECATION")
|
|
155
|
+
intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT) as? KeyEvent
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (keyEvent?.action == KeyEvent.ACTION_DOWN) {
|
|
159
|
+
when (keyEvent.keyCode) {
|
|
160
|
+
KeyEvent.KEYCODE_VOLUME_UP,
|
|
161
|
+
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
|
162
|
+
if (isRingtonePlaying) {
|
|
163
|
+
Log.d(TAG, "Volume key pressed - silencing ringtone")
|
|
164
|
+
stopRingtone()
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
"android.media.VOLUME_CHANGED_ACTION" -> {
|
|
171
|
+
// Secondary fallback for some devices
|
|
172
|
+
if (isRingtonePlaying) {
|
|
173
|
+
Log.d(TAG, "Volume changed while ringing - silencing ringtone")
|
|
174
|
+
stopRingtone()
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
169
178
|
}
|
|
170
179
|
}
|
|
171
180
|
}
|
|
172
181
|
|
|
173
|
-
private fun
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
val
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
private fun stopVolumeMonitoring() {
|
|
190
|
-
if (isMonitoringVolume) {
|
|
191
|
-
isMonitoringVolume = false
|
|
192
|
-
val context = requireContext()
|
|
193
|
-
volumeContentObserver?.let {
|
|
194
|
-
context.contentResolver.unregisterContentObserver(it)
|
|
182
|
+
private fun startVolumeKeyMonitoring() {
|
|
183
|
+
val context = requireContext()
|
|
184
|
+
if (volumeKeyReceiver != null) {
|
|
185
|
+
val filter = IntentFilter().apply {
|
|
186
|
+
addAction(Intent.ACTION_MEDIA_BUTTON)
|
|
187
|
+
addAction("android.media.VOLUME_CHANGED_ACTION")
|
|
188
|
+
priority = IntentFilter.SYSTEM_HIGH_PRIORITY
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
context.registerReceiver(volumeKeyReceiver, filter)
|
|
192
|
+
Log.d(TAG, "Volume key monitoring started")
|
|
193
|
+
} catch (e: Exception) {
|
|
194
|
+
Log.w(TAG, "Failed to register volume key receiver: ${e.message}")
|
|
195
195
|
}
|
|
196
|
-
Log.d(TAG, "Stopped volume monitoring")
|
|
197
196
|
}
|
|
198
197
|
}
|
|
199
198
|
|
|
200
|
-
private fun
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
// Stop ringtone when volume is changed
|
|
211
|
-
stopRingtone()
|
|
199
|
+
private fun stopVolumeKeyMonitoring() {
|
|
200
|
+
val context = requireContext()
|
|
201
|
+
volumeKeyReceiver?.let {
|
|
202
|
+
try {
|
|
203
|
+
context.unregisterReceiver(it)
|
|
204
|
+
Log.d(TAG, "Volume key monitoring stopped")
|
|
205
|
+
} catch (e: Exception) {
|
|
206
|
+
Log.w(TAG, "Failed to unregister volume key receiver: ${e.message}")
|
|
207
|
+
}
|
|
212
208
|
}
|
|
213
209
|
}
|
|
214
210
|
|
|
@@ -276,6 +272,61 @@ object CallEngine {
|
|
|
276
272
|
|
|
277
273
|
fun getTelecomConnection(callId: String): Connection? = telecomConnections[callId]
|
|
278
274
|
|
|
275
|
+
// --- FIXED: Audio Route Management via Telecom Callback ---
|
|
276
|
+
/**
|
|
277
|
+
* Called by MyConnection when telecom audio state changes.
|
|
278
|
+
* This is the key to solving the audio routing issue.
|
|
279
|
+
*/
|
|
280
|
+
fun onTelecomAudioStateChanged(callId: String, audioState: CallAudioState) {
|
|
281
|
+
Log.d(TAG, "Telecom audio state changed for $callId: route=${audioState.route}, muted=${audioState.isMuted}")
|
|
282
|
+
|
|
283
|
+
// If we have a desired route that differs from current, apply it now
|
|
284
|
+
desiredAudioRoute?.let { desired ->
|
|
285
|
+
val currentTelecomRoute = telecomRouteToString(audioState.route)
|
|
286
|
+
if (currentTelecomRoute != desired) {
|
|
287
|
+
Log.d(TAG, "Applying desired route $desired (current: $currentTelecomRoute)")
|
|
288
|
+
applyAudioRouteThroughTelecom(callId, desired)
|
|
289
|
+
} else {
|
|
290
|
+
Log.d(TAG, "Desired route $desired matches current route")
|
|
291
|
+
desiredAudioRoute = null // Clear since we're in sync
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Always emit the current state
|
|
296
|
+
emitAudioRouteChanged()
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private fun telecomRouteToString(route: Int): String {
|
|
300
|
+
return when (route) {
|
|
301
|
+
CallAudioState.ROUTE_BLUETOOTH -> "Bluetooth"
|
|
302
|
+
CallAudioState.ROUTE_SPEAKER -> "Speaker"
|
|
303
|
+
CallAudioState.ROUTE_WIRED_HEADSET -> "Headset"
|
|
304
|
+
CallAudioState.ROUTE_EARPIECE -> "Earpiece"
|
|
305
|
+
else -> "Unknown"
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private fun stringToTelecomRoute(route: String): Int {
|
|
310
|
+
return when (route) {
|
|
311
|
+
"Bluetooth" -> CallAudioState.ROUTE_BLUETOOTH
|
|
312
|
+
"Speaker" -> CallAudioState.ROUTE_SPEAKER
|
|
313
|
+
"Headset" -> CallAudioState.ROUTE_WIRED_HEADSET
|
|
314
|
+
"Earpiece" -> CallAudioState.ROUTE_EARPIECE
|
|
315
|
+
else -> CallAudioState.ROUTE_EARPIECE
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private fun applyAudioRouteThroughTelecom(callId: String, route: String) {
|
|
320
|
+
val connection = telecomConnections[callId]
|
|
321
|
+
if (connection != null) {
|
|
322
|
+
val telecomRoute = stringToTelecomRoute(route)
|
|
323
|
+
connection.setAudioRoute(telecomRoute)
|
|
324
|
+
Log.d(TAG, "Set audio route via telecom connection: $route")
|
|
325
|
+
} else {
|
|
326
|
+
Log.w(TAG, "No telecom connection found for $callId, cannot set audio route")
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
279
330
|
// --- Public API ---
|
|
280
331
|
fun setCanMakeMultipleCalls(allow: Boolean) {
|
|
281
332
|
canMakeMultipleCalls = allow
|
|
@@ -467,10 +518,8 @@ object CallEngine {
|
|
|
467
518
|
startForegroundService()
|
|
468
519
|
keepScreenAwake(true)
|
|
469
520
|
|
|
470
|
-
//
|
|
471
|
-
|
|
472
|
-
setInitialAudioRoute(callType)
|
|
473
|
-
}, AUDIO_ROUTE_DELAY_AFTER_ANSWER)
|
|
521
|
+
// Set desired initial route, will be applied when telecom state changes
|
|
522
|
+
setInitialAudioRoute(callType)
|
|
474
523
|
|
|
475
524
|
updateLockScreenBypass()
|
|
476
525
|
|
|
@@ -517,10 +566,8 @@ object CallEngine {
|
|
|
517
566
|
keepScreenAwake(true)
|
|
518
567
|
updateLockScreenBypass()
|
|
519
568
|
|
|
520
|
-
// FIXED:
|
|
521
|
-
|
|
522
|
-
setInitialAudioRoute(callInfo.callType)
|
|
523
|
-
}, AUDIO_ROUTE_DELAY_AFTER_ANSWER)
|
|
569
|
+
// FIXED: Set initial route via telecom system
|
|
570
|
+
setInitialAudioRoute(callInfo.callType)
|
|
524
571
|
|
|
525
572
|
if (isLocalAnswer) {
|
|
526
573
|
emitCallAnsweredWithMetadata(callId)
|
|
@@ -807,110 +854,20 @@ object CallEngine {
|
|
|
807
854
|
return AudioRoutesInfo(devices.toTypedArray(), current)
|
|
808
855
|
}
|
|
809
856
|
|
|
810
|
-
// FIXED:
|
|
857
|
+
// FIXED: New audio route approach - work with telecom system instead of against it
|
|
811
858
|
fun setAudioRoute(route: String) {
|
|
812
|
-
Log.d(TAG, "setAudioRoute
|
|
813
|
-
|
|
814
|
-
// Clear any pending route changes
|
|
815
|
-
audioRouteHandler.removeCallbacksAndMessages(null)
|
|
816
|
-
audioRouteRetryCount = 0
|
|
817
|
-
|
|
818
|
-
// Store the pending route for retry logic
|
|
819
|
-
pendingAudioRoute = route
|
|
820
|
-
|
|
821
|
-
// Execute immediately, then set up retry mechanism
|
|
822
|
-
performAudioRouteChange(route)
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
private fun performAudioRouteChange(route: String) {
|
|
826
|
-
val ctx = requireContext()
|
|
827
|
-
if (audioManager == null) {
|
|
828
|
-
audioManager = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
829
|
-
}
|
|
830
|
-
val am = audioManager!!
|
|
831
|
-
|
|
832
|
-
// Ensure proper audio mode first
|
|
833
|
-
if (am.mode != AudioManager.MODE_IN_COMMUNICATION) {
|
|
834
|
-
setAudioMode()
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
Log.d(TAG, "Performing audio route change to: $route")
|
|
838
|
-
|
|
839
|
-
when (route) {
|
|
840
|
-
"Speaker" -> {
|
|
841
|
-
// Disable Bluetooth SCO first if active
|
|
842
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && am.isBluetoothScoOn) {
|
|
843
|
-
am.stopBluetoothSco()
|
|
844
|
-
am.isBluetoothScoOn = false
|
|
845
|
-
}
|
|
846
|
-
am.isSpeakerphoneOn = true
|
|
847
|
-
Log.d(TAG, "Audio routed to SPEAKER")
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
"Earpiece" -> {
|
|
851
|
-
// Disable Bluetooth SCO if active
|
|
852
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && am.isBluetoothScoOn) {
|
|
853
|
-
am.stopBluetoothSco()
|
|
854
|
-
am.isBluetoothScoOn = false
|
|
855
|
-
}
|
|
856
|
-
am.isSpeakerphoneOn = false
|
|
857
|
-
Log.d(TAG, "Audio routed to EARPIECE")
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
"Bluetooth" -> {
|
|
861
|
-
am.isSpeakerphoneOn = false
|
|
862
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
863
|
-
am.startBluetoothSco()
|
|
864
|
-
am.isBluetoothScoOn = true
|
|
865
|
-
Log.d(TAG, "Audio routed to BLUETOOTH (SCO started)")
|
|
866
|
-
} else {
|
|
867
|
-
Log.w(TAG, "Bluetooth SCO not supported on this OS")
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
"Headset" -> {
|
|
872
|
-
am.isSpeakerphoneOn = false
|
|
873
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && am.isBluetoothScoOn) {
|
|
874
|
-
am.stopBluetoothSco()
|
|
875
|
-
am.isBluetoothScoOn = false
|
|
876
|
-
}
|
|
877
|
-
Log.d(TAG, "Audio routed to HEADSET (wired)")
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
else -> {
|
|
881
|
-
Log.w(TAG, "Unknown audio route: $route")
|
|
882
|
-
return
|
|
883
|
-
}
|
|
884
|
-
}
|
|
859
|
+
Log.d(TAG, "setAudioRoute requested: $route")
|
|
860
|
+
desiredAudioRoute = route
|
|
885
861
|
|
|
886
|
-
//
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
}
|
|
862
|
+
// Try to apply immediately if we have an active call
|
|
863
|
+
val activeCallId = activeCalls.values.find {
|
|
864
|
+
it.state == CallState.ACTIVE
|
|
865
|
+
}?.callId
|
|
890
866
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
private fun verifyAudioRoute(expectedRoute: String) {
|
|
895
|
-
val currentRoute = getCurrentAudioRoute()
|
|
896
|
-
|
|
897
|
-
if (currentRoute != expectedRoute && audioRouteRetryCount < MAX_AUDIO_ROUTE_RETRIES) {
|
|
898
|
-
audioRouteRetryCount++
|
|
899
|
-
Log.d(TAG, "Audio route verification failed. Expected: $expectedRoute, Current: $currentRoute. Retry: $audioRouteRetryCount")
|
|
900
|
-
|
|
901
|
-
// Retry the audio route change
|
|
902
|
-
audioRouteHandler.postDelayed({
|
|
903
|
-
performAudioRouteChange(expectedRoute)
|
|
904
|
-
}, AUDIO_ROUTE_RETRY_DELAY)
|
|
867
|
+
if (activeCallId != null) {
|
|
868
|
+
applyAudioRouteThroughTelecom(activeCallId, route)
|
|
905
869
|
} else {
|
|
906
|
-
|
|
907
|
-
Log.d(TAG, "Audio route successfully verified: $currentRoute")
|
|
908
|
-
} else {
|
|
909
|
-
Log.w(TAG, "Audio route change failed after $MAX_AUDIO_ROUTE_RETRIES retries. Expected: $expectedRoute, Current: $currentRoute")
|
|
910
|
-
}
|
|
911
|
-
// Clear pending route
|
|
912
|
-
pendingAudioRoute = null
|
|
913
|
-
audioRouteRetryCount = 0
|
|
870
|
+
Log.d(TAG, "No active call found, route will be applied when call becomes active")
|
|
914
871
|
}
|
|
915
872
|
}
|
|
916
873
|
|
|
@@ -1343,21 +1300,23 @@ object CallEngine {
|
|
|
1343
1300
|
}
|
|
1344
1301
|
|
|
1345
1302
|
// --- Media Management ---
|
|
1346
|
-
// FIXED: Improved ringtone
|
|
1303
|
+
// FIXED: Improved ringtone with volume key detection
|
|
1347
1304
|
private fun playRingtone() {
|
|
1348
1305
|
val context = requireContext()
|
|
1349
1306
|
|
|
1350
1307
|
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
1351
1308
|
|
|
1309
|
+
// Set ringtone playing flag
|
|
1310
|
+
isRingtonePlaying = true
|
|
1311
|
+
|
|
1352
1312
|
// Set up proper audio mode for ringtone
|
|
1353
1313
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
1354
1314
|
audioManager?.mode = AudioManager.MODE_RINGTONE
|
|
1355
1315
|
}
|
|
1356
1316
|
audioManager?.isSpeakerphoneOn = true
|
|
1357
1317
|
|
|
1358
|
-
// Start volume monitoring
|
|
1359
|
-
|
|
1360
|
-
lastRingVolumeLevel = audioManager?.getStreamVolume(AudioManager.STREAM_RING) ?: 0
|
|
1318
|
+
// Start volume key monitoring
|
|
1319
|
+
startVolumeKeyMonitoring()
|
|
1361
1320
|
|
|
1362
1321
|
// Start repeating vibration
|
|
1363
1322
|
vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
|
|
@@ -1383,6 +1342,8 @@ object CallEngine {
|
|
|
1383
1342
|
}
|
|
1384
1343
|
|
|
1385
1344
|
fun stopRingtone() {
|
|
1345
|
+
isRingtonePlaying = false
|
|
1346
|
+
|
|
1386
1347
|
try {
|
|
1387
1348
|
ringtone?.stop()
|
|
1388
1349
|
Log.d(TAG, "Ringtone stopped")
|
|
@@ -1395,8 +1356,8 @@ object CallEngine {
|
|
|
1395
1356
|
vibrator?.cancel()
|
|
1396
1357
|
vibrator = null
|
|
1397
1358
|
|
|
1398
|
-
// Stop volume monitoring
|
|
1399
|
-
|
|
1359
|
+
// Stop volume key monitoring
|
|
1360
|
+
stopVolumeKeyMonitoring()
|
|
1400
1361
|
}
|
|
1401
1362
|
|
|
1402
1363
|
private fun startRingback() {
|
|
@@ -1433,10 +1394,9 @@ object CallEngine {
|
|
|
1433
1394
|
stopForegroundService()
|
|
1434
1395
|
keepScreenAwake(false)
|
|
1435
1396
|
resetAudioMode()
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
audioRouteRetryCount = 0
|
|
1397
|
+
stopVolumeKeyMonitoring()
|
|
1398
|
+
desiredAudioRoute = null
|
|
1399
|
+
isWaitingForTelecomAudioState = false
|
|
1440
1400
|
}
|
|
1441
1401
|
|
|
1442
1402
|
// --- Lifecycle Management ---
|
|
@@ -130,4 +130,22 @@ class MyConnection(
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
+
|
|
134
|
+
override fun onCallAudioStateChanged(state: CallAudioState) {
|
|
135
|
+
super.onCallAudioStateChanged(state)
|
|
136
|
+
Log.d(TAG, "Audio state changed for callId: $callId. muted=${state.isMuted}, route=${state.route}")
|
|
137
|
+
|
|
138
|
+
if (lastAudioState == null || lastAudioState!!.isMuted != state.isMuted) {
|
|
139
|
+
if (state.isMuted) {
|
|
140
|
+
CallEngine.muteCall(callId)
|
|
141
|
+
} else {
|
|
142
|
+
CallEngine.unmuteCall(callId)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// FIXED: Notify CallEngine of telecom audio state changes
|
|
147
|
+
CallEngine.onTelecomAudioStateChanged(callId, state)
|
|
148
|
+
|
|
149
|
+
lastAudioState = state
|
|
150
|
+
}
|
|
133
151
|
}
|