Skip to content

Commit cf42af3

Browse files
authored
fix: audio mode set for miui 12 legacy routing (WT-1489) (#339)
* fix: audio mode set for miui 12 legacy routing * fix: request audio focus for oem routing
1 parent 8647f28 commit cf42af3

1 file changed

Lines changed: 71 additions & 0 deletions

File tree

  • webtrit_callkeep_android/android/src/main/kotlin/com/webtrit/callkeep/services/services/connection

webtrit_callkeep_android/android/src/main/kotlin/com/webtrit/callkeep/services/services/connection/PhoneConnection.kt

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.webtrit.callkeep.services.services.connection
22

33
import android.content.Context
4+
import android.media.AudioAttributes
5+
import android.media.AudioFocusRequest
46
import android.os.Build
57
import android.os.Handler
68
import android.os.Looper
@@ -44,6 +46,19 @@ class PhoneConnection internal constructor(
4446
private var isMute = false
4547
private var isHasSpeaker = false
4648

49+
/**
50+
* True when this connection explicitly set MODE_IN_COMMUNICATION because the OEM Telecom
51+
* (e.g. MIUI) skipped audio-focus setup for outgoing calls. Used to know whether we need
52+
* to reset the audio mode on disconnect.
53+
*/
54+
private var audioModeForced = false
55+
56+
/**
57+
* Audio focus request held alongside [audioModeForced], for the same OEM Telecom
58+
* workaround — released together with the forced mode reset on disconnect.
59+
*/
60+
private var forcedAudioFocusRequest: AudioFocusRequest? = null
61+
4762
/**
4863
* Tracks whether the speaker was manually disabled by the user.
4964
* Prevents automatic re-enabling during video calls.
@@ -202,6 +217,23 @@ class PhoneConnection internal constructor(
202217

203218
dispatcher(eventForDisconnectCause(disconnectCause), metadata)
204219
onDisconnectCallback.invoke(this)
220+
221+
// If we forced MODE_IN_COMMUNICATION in onActiveConnection(), restore NORMAL mode now
222+
// that this call is gone. Only reset if no other active/holding calls remain so we
223+
// don't disrupt a concurrent call that also needs the VoIP audio path.
224+
if (audioModeForced && !PhoneConnectionService.connectionManager.hasActiveOrHoldingConnection()) {
225+
val sysAm = context.getSystemService(android.content.Context.AUDIO_SERVICE) as android.media.AudioManager
226+
sysAm.mode = android.media.AudioManager.MODE_NORMAL
227+
logger.d("onDisconnect: reset audio mode to MODE_NORMAL (last active call ended)")
228+
229+
forcedAudioFocusRequest?.let {
230+
sysAm.abandonAudioFocusRequest(it)
231+
logger.d("onDisconnect: abandoned call audio focus (OEM workaround)")
232+
}
233+
forcedAudioFocusRequest = null
234+
}
235+
audioModeForced = false
236+
205237
destroy()
206238
}
207239

@@ -634,6 +666,45 @@ class PhoneConnection internal constructor(
634666

635667
enforceVideoSpeakerLogic()
636668

669+
// On OEM Telecom implementations (e.g. MIUI Android 9-12) that skip audio-focus setup
670+
// for outgoing self-managed calls, callAudioState is never delivered and
671+
// AudioManager.setMode(MODE_IN_COMMUNICATION) is never called. Without that mode,
672+
// WebRTC's AudioRecord(VOICE_COMMUNICATION) and AudioTrack(STREAM_VOICE_CALL) cannot
673+
// access the voice call path on these devices — both mic capture and speaker playback
674+
// stall completely. Explicitly set the mode here so WebRTC audio works.
675+
// On AOSP, callAudioState is non-null (Telecom already set the mode), so the guard
676+
// keeps this change inert on standard Android.
677+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE && callAudioState == null) {
678+
val sysAm = context.getSystemService(android.content.Context.AUDIO_SERVICE) as android.media.AudioManager
679+
if (sysAm.mode != android.media.AudioManager.MODE_IN_COMMUNICATION) {
680+
sysAm.mode = android.media.AudioManager.MODE_IN_COMMUNICATION
681+
audioModeForced = true
682+
logger.d("onActiveConnection: forced MODE_IN_COMMUNICATION (callAudioState null — OEM workaround)")
683+
}
684+
685+
// Telecom normally requests call audio focus for us when a self-managed connection
686+
// becomes active (CallAudioModeStateMachine); the same OEM bug that skips setMode()
687+
// also skips this. Without it, another app's audio focus can keep suppressing
688+
// WebRTC's stream even after MODE_IN_COMMUNICATION is set. requestAudioFocusForCall()
689+
// is a hidden @SystemApi we can't call, so mirror the intent with the public API.
690+
if (forcedAudioFocusRequest == null) {
691+
val attributes = AudioAttributes.Builder()
692+
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
693+
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
694+
.build()
695+
val request = AudioFocusRequest.Builder(android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
696+
.setAudioAttributes(attributes)
697+
.build()
698+
val result = sysAm.requestAudioFocus(request)
699+
if (result == android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
700+
forcedAudioFocusRequest = request
701+
logger.d("onActiveConnection: acquired call audio focus (OEM workaround)")
702+
} else {
703+
logger.w("onActiveConnection: audio focus request denied (result=$result)")
704+
}
705+
}
706+
}
707+
637708
// Proactively emit audio device state for OEM Telecom implementations that do not
638709
// call setCallAudioState for outgoing calls (e.g. MIUI Android 12). On AOSP,
639710
// CallAudioRouteStateMachine.resendSystemAudioState() fires during call setup and

0 commit comments

Comments
 (0)