
Key takeaways
• setCommunicationDevice() is the 2026 default for audio output switching. Every new Android 12+ call feature should route through AudioManager.setCommunicationDevice and AudioDeviceCallback. The legacy startBluetoothSco / setSpeakerphoneOn pair is deprecated and should live only inside an SDK-level fallback.
• OEM fragmentation is the real budget eater. Pixel, Samsung, Xiaomi, OPPO and Huawei all behave slightly differently on Bluetooth SCO and audio focus. Plan 2–3 weeks of real-device testing, not 2 hours of emulator time.
• Android 14–17 pulled in the reins. Foreground service type phoneCall is mandatory, BLUETOOTH_CONNECT must be granted before touching the adapter, and LE Audio + hearing-aid routing land as new AudioDeviceInfo types you must handle in your UI. Android 16 (API 36) adds Auracast broadcast audio and lets hearing-aid users switch mics mid-call. Android 17 (API 37, June 2026) hardens background audio: without a visible activity or a qualifying foreground service, playback goes silent and requestAudioFocus() fails.
• If you use WebRTC, LiveKit or Agora, most of this is already solved. A custom AudioDeviceModule belongs inside your SDK boundary, not in every feature team. If Telecom owns the call through Core-Telecom, switch outputs with requestEndpointChange(), never with setCommunicationDevice(). Match the hire to the layer.
• Fora Soft has shipped this module more times than we can count. Virtual classrooms, telemedicine, consumer calls — we’ll share the Kotlin wrapper we reuse across clients, plus the OEM test matrix.
Updated October 2026: Android 17 (API 37) background-audio hardening, Core-Telecom endpoint switching, Google Play target API 36 now in force, corrected foreground-service exception and API levels.
Why Fora Soft wrote this playbook
Audio routing on Android looks like a 40-line AudioManager wrapper. Then you test on a real Xiaomi device, a fresh Samsung, a user on Android 11 in Brazil and a hearing-aid owner on a Pixel 9, and you discover the 40 lines are 400. This guide is the Kotlin playbook we use on every voice/video Android build in our video chat app development work at Fora Soft — tuned to the 2026 reality of API 31+ APIs, foreground-service-type enforcement, Bluetooth LE Audio and a tighter permission model.
Since 2005 we’ve shipped 250+ voice, video and AI products, from video calling and conferencing apps to telemedicine, with a 99% Upwork success rate. Our BrainCert virtual classroom ships a native Android client that routes hundreds of thousands of in-call minutes per week across headsets, speakers, car systems and hearing aids. CirrusMED’s HIPAA-grade telemedicine app handles the same routing in a regulated setting. The recommendations below come from those builds.
Planning an Android call app?
Book a 30-minute call with our Android lead. We’ll share our AudioDeviceManager wrapper, the OEM test matrix, and a realistic delivery window for your use case.
The problem space in 2026
Audio output switching is the act of moving a live call’s sound between outputs on the fly. Android audio routing is the same job seen from the system side: deciding which output and which microphone a call stream uses at any moment. In a real user session your app may need to flip the active audio output mid-call between the earpiece, the built-in speaker, wired 3.5mm or USB-C headsets, Bluetooth SCO headsets, Bluetooth LE Audio devices, hearing aids, and a paired car system — often without user action (“headphones plugged in”) and often while the OS is itself in the middle of a connect/disconnect animation.
Three forces make this harder than it looks. First, Android’s public API surface changed twice between 2021 and 2025: API 31 introduced setCommunicationDevice() and deprecated a handful of old calls; API 33 added LE Audio; API 34–35 tightened the service and permission models. Second, OEM audio stacks (MIUI, One UI, ColorOS, EMUI) quietly alter SCO timing, audio focus and ringtone behavior. Third, modern call apps are almost always WebRTC apps, which bring their own AudioDeviceModule and its own interaction rules with the OS stack.

Figure 1. How Android’s audio-routing APIs evolved — write new code against setCommunicationDevice (API 31); Android 16 adds Auracast.
Why this matters for the business, not just the engineer
Android accounted for about 75% of global smartphone sales in Q2 2026, down from 79% a year earlier, according to Counterpoint Research figures. For any voice or video product that isn’t iOS-only, audio routing quality is one of the five variables that decide review scores and retention — alongside latency, crash rate, camera quality and call-setup time. We see 1-star reviews referencing “no sound on Bluetooth” or “speaker doesn’t switch” on almost every new Android build that didn’t budget time for the OEM test matrix.
In practical terms: an audio-routing bug costs you about as much user goodwill as a hard crash, but with lower visibility in your monitoring stack. The 2–3-week testing investment we recommend below isn’t engineering perfectionism — it’s a churn reduction project.
The 2026 Android audio API map
The short version: for anything you write in 2026, audio output switching on Android runs through setCommunicationDevice() (API 31, Android 12), and everything below API 31 is a fallback. Android 17 (API 37, stable since June 16, 2026) is the newest link: it hardens background audio for every app, covered in the permissions section below. Android 16 (API 36, 2025) added Auracast broadcast audio and lets hearing-aid users switch between their hearing-aid mics and the phone mic mid-call. Since August 31, 2026, Google Play requires new phone apps and updates to target API 36 (extensions run to November 1, 2026), and existing apps must target at least API 35 to stay visible to new users on newer devices. Treat the table below as live, not history.
| API level | Preferred API | Key additions | Gotchas |
|---|---|---|---|
| 21–30 (Legacy) | setSpeakerphoneOn + startBluetoothSco | AudioDeviceCallback (API 23+) | SCO connect timing unpredictable; no standard API |
| 31–32 (Android 12) | setCommunicationDevice() | Unified device selection, BLUETOOTH_CONNECT runtime perm | Old APIs deprecated but still respected on many OEMs |
| 33 (Android 13) | setCommunicationDevice() + LE Audio types | TYPE_BLE_BROADCAST (BLE headset/speaker types exist since API 31, TYPE_HEARING_AID since API 28) | LE Audio rollout uneven across vendors |
| 34 (Android 14) | Same + phoneCall foreground service type | Mandatory foregroundServiceType, stricter BT checks | Missing type throws MissingForegroundServiceTypeException |
| 35 (Android 15) | Same + Audio Sharing / LE Audio groups | CTA-2075 loudness, shared LE Audio groups | Broadcast / group routing still experimental |
| 36 (Android 16) | Same + Auracast broadcast audio | Hearing-aid mic switch, ambient volume, audio sharing, TYPE_MULTICHANNEL_GROUP | targetSdk 36 required by Google Play since Aug 31, 2026 |
| 37 (Android 17) | Same + background audio hardening | Playback, focus and volume calls need a visible activity or a non-short foreground service | Blocked playback is silent; requestAudioFocus() returns AUDIOFOCUS_REQUEST_FAILED |
The modern pattern — setCommunicationDevice() end to end
AudioManager.setCommunicationDevice(device) (API 31+) tells Android which output and microphone to use for communication audio; it returns a boolean right away, and the route is confirmed asynchronously. For API 31+ the canonical flow Google documents is: set the mode → request audio focus → register a device callback → enumerate available devices → call setCommunicationDevice(chosen) → update your UI reactively when the callback fires → clear the device on tear-down.

Figure 2. The setCommunicationDevice() call lifecycle, from call start to tear-down.
class AudioDeviceManager(
private val context: Context
) {
private val audioManager =
context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
private val _devices = MutableStateFlow<List<AudioDeviceInfo>>(emptyList())
val devices: StateFlow<List<AudioDeviceInfo>> = _devices
private val _selected = MutableStateFlow<AudioDeviceInfo?>(null)
val selected: StateFlow<AudioDeviceInfo?> = _selected
private val callback = object : AudioDeviceCallback() {
override fun onAudioDevicesAdded(added: Array<AudioDeviceInfo>) = refresh()
override fun onAudioDevicesRemoved(removed: Array<AudioDeviceInfo>) = refresh()
}
// confirms that the route was actually applied
private val routeListener =
AudioManager.OnCommunicationDeviceChangedListener { device ->
_selected.value = device
}
fun startCall() {
audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
audioManager.registerAudioDeviceCallback(callback, Handler(Looper.getMainLooper()))
audioManager.addOnCommunicationDeviceChangedListener(
context.mainExecutor, routeListener)
refresh()
}
fun select(device: AudioDeviceInfo): Boolean =
// true = request accepted; routeListener fires when it is applied
audioManager.setCommunicationDevice(device)
fun endCall() {
audioManager.clearCommunicationDevice()
audioManager.unregisterAudioDeviceCallback(callback)
audioManager.removeOnCommunicationDeviceChangedListener(routeListener)
audioManager.mode = AudioManager.MODE_NORMAL
}
private fun refresh() {
_devices.value = audioManager.availableCommunicationDevices
_selected.value = audioManager.communicationDevice
}
}
The whole component sits behind a Kotlin StateFlow interface, which means your Jetpack Compose call screen gets re-rendered cleanly when the user plugs in a headset or the car connects mid-drive. That reactive shape is what makes the rest of your routing code boring — which is exactly what you want.
Two details from Google’s guide that teams skip. A true from setCommunicationDevice() only means the request was accepted, so update the UI from OnCommunicationDeviceChangedListener, not from the return value. And Google allows up to 30 seconds for the route to apply; on an error it recommends clearCommunicationDevice() and then a fresh setCommunicationDevice(). For the user, we still fall back to the speaker after about 8 seconds (see the Bluetooth section) and keep listening for the late confirmation.
Legacy fallback for API 21–30
If you still support Android 11 or below — often the case for consumer apps in emerging markets — you need the old calls as a second path. Put the modern path inside if (Build.VERSION.SDK_INT >= 31) and keep the old calls in the else branch so they don’t pollute it. How many users that branch serves: in September 2026 about 85% of Android devices in StatCounter’s version data ran Android 12 or newer and 6.4% ran Android 11, so a minSdk of 31 drops roughly one user in seven.
@Suppress("DEPRECATION")
fun legacySetBluetoothSco(enable: Boolean) {
if (enable) {
audioManager.startBluetoothSco()
audioManager.isBluetoothScoOn = true
audioManager.isSpeakerphoneOn = false
} else {
audioManager.isBluetoothScoOn = false
audioManager.stopBluetoothSco()
}
}
Bluetooth SCO, LE Audio and hearing aids
SCO is the narrow-band voice link used by traditional Bluetooth headsets and most car kits. On Pixel devices it typically connects within 1–2 seconds; on Xiaomi Redmi we’ve seen 5–8 second delays before the audio actually routes. Always implement a timeout that falls back to the built-in speaker if the callback hasn’t confirmed the route within ~8 seconds — users will blame the app, not the phone.
LE Audio appears as TYPE_BLE_HEADSET / TYPE_BLE_SPEAKER (constants since API 31; Google’s migration guide makes Android 13 the point where call apps must move to setCommunicationDevice()). Stereo, lower latency, better battery, and the LC3 codec underneath it — the same audio-quality thinking we walk through in our audio engineering primer. It’s the future but coverage is uneven — treat it as available-when-available and keep SCO as the baseline.
Hearing aids use ASHA (Google’s pre-LE Audio protocol) or the new LE Audio hearing-aid profile. TYPE_HEARING_AID should always be treated as a first-class routing option, not an afterthought — our AI-powered multimedia e-learning guide covers the accessibility expectations we bake in by default. On Android 16, LE Audio hearing aids can also receive Auracast broadcasts and switch between the hearing-aid mics and the phone mic mid-call — surface that choice in your device picker.
Reach for an SCO timeout path when: any of your target devices are Xiaomi, OPPO or Realme. A single 8-second postDelayed fallback to the speaker eliminates the vast majority of “no sound on Bluetooth” tickets.
Wired 3.5mm, USB-C and accessory audio
Wired audio feels like the simple case but has its own fragility. Flagship Samsung and Pixel phones haven’t shipped a 3.5mm jack in years, so most wired headsets arrive via USB-C or a DAC adapter. The modern API exposes them as TYPE_USB_HEADSET, TYPE_WIRED_HEADSET, or TYPE_WIRED_HEADPHONES (the last meaning “no microphone” — treat this as an accessibility signal).
Never rely on the deprecated isWiredHeadsetOn or the old Intent.ACTION_HEADSET_PLUG broadcast. Both are flaky on modern OEM ROMs. Route all detection through AudioDeviceCallback.
Permissions, foreground services and the 2024–2026 tightening
BLUETOOTH_CONNECT is a runtime permission since API 31. Missing the grant means setCommunicationDevice() on a BT device silently fails on some OEMs and throws on others. Gate your Bluetooth UI behind the permission request, then re-enumerate devices.
Foreground service types became mandatory in API 34 (Android’s own audio-routing API notes spell out the change). A call app must declare:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<service
android:name=".VoIPService"
android:foregroundServiceType="phoneCall"
android:exported="false" />
Missing foregroundServiceType="phoneCall" means startForeground() throws MissingForegroundServiceTypeException for apps targeting API 34+, and the service never starts. The FOREGROUND_SERVICE_PHONE_CALL permission must be declared too, and the phoneCall type has a runtime prerequisite: the app holds MANAGE_OWN_CALLS or is the default dialer, per Google’s foreground service types reference.
Android 17 background audio hardening (API 37, June 2026) applies to every app running on Android 17, whatever its target. Playback, requestAudioFocus() and volume calls made from the background need a visible activity (picture-in-picture counts) or a foreground service other than shortService; apps that target API 37 also need a foreground service with while-in-use capability. Blocked playback is silent with no exception, and focus requests return AUDIOFOCUS_REQUEST_FAILED, according to Google’s background audio hardening notes. Calls that run through the Telecom APIs are unlikely to be affected. The risky case is a hand-rolled VoIP service started while the app is in the background. Test with adb shell cmd audio set-enable-hardening throw and search logcat for AudioHardening.
Reach for Core-Telecom (the Jetpack layer over ConnectionService / Telecom) when: your app handles actual phone-style calls (incoming/outgoing, ringer integration, heads-up call UI). Telecom wires routing into the system UI and saves you a whole class of edge cases — for VoIP SaaS it’s overkill, for a consumer call app it’s usually the right path.
Core-Telecom: when Telecom owns the audio route
If your call is registered with Telecom through the Jetpack Core-Telecom library (androidx.core:core-telecom), Telecom owns routing, and you switch outputs with CallControlScope.requestEndpointChange() instead of AudioManager. Google’s guide is explicit: don’t call setCommunicationDevice() or startBluetoothSco() while Telecom manages the call, or you get audio issues.
// inside the CallControlScope block of CallsManager.addCall(...)
launch { availableEndpoints.collect { endpoints -> ui.showDevices(endpoints) } }
launch { currentCallEndpoint.collect { endpoint -> ui.markSelected(endpoint) } }
// user taps a device in the in-call picker
launch { requestEndpointChange(pickedEndpoint) }
Core-Telecom uses ConnectionService on Android 13 and lower and the newer Telecom APIs on Android 14+, and Google asks for STREAM_VOICE_CALL on the media stream. The trade-off: you gain system call UI, Bluetooth accept/reject and car integration, and you give up direct control of the route. Pick one owner per call — Telecom or your own AudioDeviceManager — never both.
A note on InCallService, which shows up in many routing searches: it is the API the default dialer (and similar privileged roles) uses to display and control calls. It is not a way for a VoIP app to route another app’s audio, and third-party apps can’t inject TTS or other audio into a carrier call’s uplink.
Audio focus: the invisible requirement
Every call app needs to own audio focus for the duration of the call. Failing to request it cleanly leaves music apps fighting for the output and produces the classic “stuck under Spotify” bug on Xiaomi MIUI devices. Use AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE for short calls that should pause other audio, or AUDIOFOCUS_GAIN for long-duration sessions.
val req = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
)
.setOnAudioFocusChangeListener { /* pause/duck/restore */ }
.build()
val granted = audioManager.requestAudioFocus(req) ==
AudioManager.AUDIOFOCUS_REQUEST_GRANTED
// Android 17+: a background request without a qualifying
// foreground service returns AUDIOFOCUS_REQUEST_FAILED
WebRTC, LiveKit and Agora — where the routing really lives
If you’re using WebRTC (directly or via a wrapper SDK), your audio routing belongs inside a custom JavaAudioDeviceModule or the SDK’s equivalent. The public AudioManager sets up the system-level path; the SDK’s AudioDeviceModule owns how the WebRTC engine opens input and output streams. Get these two layers out of sync and you’ll see half-muted audio, echo, or no sound at all.
LiveKit Android SDK ships an AudioSwitchHandler built on AudioSwitch: preferredDeviceList sets the priority, selectDevice() overrides it and survives hot-plug, and forceHandleAudioRouting covers phones where Bluetooth mics only work in communication mode. When our LiveKit development team builds on it, the in-call device picker talks to that handler, not to AudioManager. Our Agora vs WebRTC piece covers the trade-offs.
Twilio’s AudioSwitch library is open source and the clearest reference for a hand-rolled routing layer. Agora’s SDK hides routing behind setEnableSpeakerphone and friends but handles OEM fragmentation internally — which is part of why you pay for it.
Need this working across 20 OEM devices by next sprint?
We keep a standing device lab with Pixel, Samsung, Xiaomi, OPPO, OnePlus and Huawei phones across the Android versions your users actually run. Send us your build and we’ll run the whole audio-routing matrix in under a week.
OEM fragmentation — what your test matrix should cover
The single highest-impact investment after you’ve written the routing code is a real-device matrix covering the vendors your users actually hold. The worst issues in 2025–2026 cluster around MIUI, ColorOS and One UI customizations.

Figure 3. Typical vs worst-case Bluetooth SCO connect delay we see per OEM skin.
| OEM / skin | Typical SCO delay | Audio-focus quirks | Our recommendation |
|---|---|---|---|
| Google Pixel (stock) | ~1–2 s | Predictable | Baseline device for CI |
| Samsung One UI | ~2–3 s | Usually fine | Test Galaxy A + S flagship |
| Xiaomi MIUI / HyperOS | ~5–8 s | Music apps steal focus | SCO timeout + explicit focus |
| OPPO / Realme ColorOS | ~4–6 s | Aggressive battery saver kills callbacks | Foreground service + SCO timeout |
| OnePlus OxygenOS | ~2–4 s | Mostly fine | Standard |
| Huawei EMUI / HarmonyOS | ~3–6 s | No Google Play — AppGallery only | Test if you ship in China/MEA; HarmonyOS 5 (NEXT) phones don’t run Android apps at all |
Reach for a managed SDK when: your team has no prior WebRTC experience, your launch is under 10 weeks, or voice/video is a feature rather than the product. LiveKit, Agora and Twilio have already paid the OEM fragmentation tax on your behalf.
Reference architecture — MVVM + StateFlow + Jetpack Compose
Sketch of how the pieces fit together in a modern Android call app:

Figure 4. Reference architecture: the ViewModel is the single source of truth for the selected device.
Two principles we never compromise on. One source of truth for the selected device (the ViewModel); one reactive stream for the available devices (the StateFlow). Push both into every consumer, including the WebRTC AudioDeviceModule, and the mid-call routing bugs stop happening.
Reach for Hilt dependency injection when: your AudioDeviceManager needs to be testable with Robolectric or swappable per build flavor (prod vs staging audio mocks). It’s a 30-minute setup that pays back across every unit test.
Testing — unit, instrumented and device farm
Unit tests with Robolectric cover the state-machine logic: “when the callback fires with a new BT headset, does the StateFlow emit the right value?” Fast, cheap, catch 60% of regressions.
Instrumented tests on Android emulators are weak for audio because emulators fake most of the stack. Use them only for smoke tests; don’t trust them for routing behavior.
Real-device regression. This is where most of the budget goes. We keep an internal matrix covering 6 OEM skins × 3 Android versions × 3 Bluetooth accessories (cheap headset, AirPods-class BT, car speakerphone). A full pass is ~4 hours of manual testing per major release.
Device-farm services (Firebase Test Lab, BrowserStack App Live) close the gap for quick sanity checks. They don’t replace a real-person test on a real Bluetooth headset, but they catch crashes and permission misconfigurations well before you ship.
Mini case — rebuilding the audio layer in 3 weeks
An EU telemedicine client came to us after shipping a WebRTC call app with a 2.7-star Android rating. The complaints clustered around Bluetooth handovers — calls starting on the speaker, switching to the car when the driver got in, and never routing back when the driver left. They also had a spike in support tickets from users with hearing aids.
Our three-week rebuild replaced a homegrown Broadcast-Receiver approach with a Kotlin AudioDeviceManager built on setCommunicationDevice() and AudioDeviceCallback, added an 8-second SCO fallback, wired TYPE_HEARING_AID into the in-call UI, declared foregroundServiceType="phoneCall", and kept the legacy SCO path behind a version check for Android 11 users. QA ran 6 hours of manual tests on 14 devices. Result: rating climbed to 4.3 over the next quarter, audio-routing support tickets dropped about 78% week-over-week. Want a similar assessment for your stack? Book 30 minutes and we’ll sketch it.
A decision framework — pick your audio-routing path in five questions

Figure 5. Pick your audio-routing path: the first Yes wins.
Q1. Are you using a WebRTC SDK? If yes (LiveKit, Agora, Twilio), let the SDK own the routing, wire your UI to its audio-device API, and only reach for raw AudioManager for gaps.
Q2. What’s your min-SDK target? API 31 lets you write a single modern code path; below that you need the legacy fallback behind a version check. Set your min-SDK deliberately.
Q3. Is this a “true” call app or an in-app voice session? True call apps should plug into ConnectionService / TelecomManager. Voice sessions in a SaaS (support calls, meetings) can stay on AudioManager with a foreground service.
Q4. Accessibility requirements? Healthcare, education and government tilt towards hearing-aid and LE Audio support from day one. If the answer is yes, budget device testing with real hearing aids, not just headsets.
Q5. Which OEMs matter? Test on the vendors your users actually carry. “Android” isn’t a device; 60% of your users might be on MIUI or ColorOS and the code that works on Pixel may not work there.
Six pitfalls we see every quarter
1. Relying on ACTION_HEADSET_PLUG or isWiredHeadsetOn. Both are deprecated and inconsistent. AudioDeviceCallback is the answer on every API from 23 up.
2. Forgetting BLUETOOTH_CONNECT. The permission is silent on success and noisy on failure depending on OEM. Gate your Bluetooth UI on the grant and re-enumerate after acceptance.
3. Missing foregroundServiceType="phoneCall". API 34+ throws on startForeground. It’s a two-line fix and a weekend of missed crashes without it.
4. No SCO timeout fallback. On Xiaomi and OPPO, 5–8 seconds of silence feels like an app bug even if SCO eventually connects. Always fall back to the built-in speaker after ~8 seconds.
5. Mismatched AudioManager and WebRTC AudioDeviceModule state. Two systems owning the same route produce ghost echo and half-muted audio. Single source of truth, propagated to both.
6. Calling setCommunicationDevice() while Telecom owns the call. With Core-Telecom, every route change goes through requestEndpointChange(). Mixing the two is the audio-issue case Google’s guide warns about.
KPIs: how to measure a healthy audio layer
Quality KPIs. Median and P95 time from “BT device connected” to “audio actually routed” (target P95 <4 s on all OEMs), share of calls where user changed the audio device at least once (healthy baseline 20–40%), and audio-routing-related crash rate (<0.1% of sessions).
Business KPIs. Support-ticket volume tagged “no sound / sound issues”, Play Store reviews with audio-related keywords, retention delta between users with and without Bluetooth paired (should trend flat; a gap usually means your routing is broken for that segment).
Reliability KPIs. Rate of AudioDeviceCallback events per 1,000 call-minutes (under-counting suggests OS killing your service), rate of audio-focus loss transitions you failed to handle, and rate of unexpected MODE_NORMAL transitions during a call.
When not to build this yourself
Skip a custom audio layer and lean on a managed SDK (LiveKit, Agora, Twilio) if (a) your team has no prior WebRTC experience, (b) call volume is low enough that the SDK per-minute fee is trivial, (c) you’re shipping a SaaS feature rather than a dedicated call product, or (d) your user base looks like every OEM on the planet and you can’t realistically maintain a device-test matrix. The SDKs have paid for their fragmentation tax in a way you don’t want to replicate unless you have to.
FAQ
How do I switch audio output on Android programmatically?
On Android 12+ (API 31), list availableCommunicationDevices, call setCommunicationDevice() with the device the user picked, confirm the switch in OnCommunicationDeviceChangedListener, and call clearCommunicationDevice() when the call ends. If Core-Telecom manages the call, use requestEndpointChange() instead.
Does Android 17 break call audio in the background?
Only if the app has neither a visible activity nor a qualifying foreground service: Android 17 then silences playback and fails requestAudioFocus() for every app, whatever its target SDK. Calls managed through the Telecom APIs are unlikely to be affected.
Can an app route or inject audio into a regular phone call with InCallService?
No. InCallService is for the default dialer and similar privileged roles, and third-party apps can’t inject TTS or other audio into a carrier call’s uplink. A VoIP app routes its own calls through AudioManager or Core-Telecom.
Is startBluetoothSco() still safe to use in 2026?
It’s deprecated from API 31 but still respected on most OEMs. Keep it only as a legacy fallback inside a Build.VERSION.SDK_INT < 31 branch. For anything new, use setCommunicationDevice().
How do I detect LE Audio support at runtime?
Check API 31+ and enumerate availableCommunicationDevices for TYPE_BLE_HEADSET or TYPE_BLE_SPEAKER. If neither appears, the device or its firmware doesn’t support LE Audio for communication and you should fall back to SCO.
Do I need ConnectionService?
Only if your app behaves like a phone — incoming/outgoing calls that should integrate with the system dialer, ringer, Bluetooth accept/reject. For typical in-app VoIP or meetings, a foreground service with AudioManager is enough and simpler.
How long does a production audio-routing module take to build?
A proof of concept on a single OEM is 2–5 days. A production-grade implementation with OEM testing, legacy fallback, permission UX, foreground-service wiring and WebRTC integration is 2–4 weeks for a senior Android engineer. A full in-call audio UI plus monitoring adds another 1–2 weeks.
Why is my app silent on Bluetooth on Xiaomi specifically?
Usually SCO connect delay or audio focus being held by a music app. Add an 8-second SCO timeout fallback to the speaker, request AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE, and re-check that MIUI’s aggressive battery saver isn’t killing your foreground service.
Should you use LiveKit, Agora or Twilio — or build it yourself?
If voice/video is your core product and you have senior Android + WebRTC talent, owning the stack gives you control and eliminates per-minute fees at scale. If voice is a feature in a larger app, or if you want to ship in 6–10 weeks with a smaller team, lean on a managed SDK — they’ve already solved the OEM fragmentation you would otherwise inherit. Our piece on hiring LiveKit developers is a good place to start.
How do I make sure hearing-aid users get a good experience?
Surface TYPE_HEARING_AID in the audio-device picker with a clear label, test with a real ASHA or LE-Audio hearing aid, avoid assuming the device is the user’s default — they may have paired both a headset and hearing aids — and follow our accessibility checklist for broader coverage.
What about iOS feature parity?
iOS exposes similar concepts via AVAudioSession and is typically less fragmented. A single Android routing bug often takes longer to fix than an entire iOS audio layer — budget accordingly. For cross-platform flutter/ React Native apps, bridge the platform layer into a common Kotlin/Swift surface rather than trying to abstract both into JS.
What to read next
Hiring
How to hire LiveKit developers
The WebRTC + voice AI skill matrix, interview questions and rate benchmarks for 2026.
WebRTC
Building a video call app with Agora SDK in 2026
Where a managed SDK takes over and where you still own the audio routing.
Video conferencing
How to build custom video conferencing solutions
End-to-end view of what goes into a production conferencing stack.
Android distribution
Distributing Android apps beyond Google Play
Alt-store distribution strategies once your call app is ready to ship.
Voice UX
Enhancing UX with speech recognition and NLP
The companion piece on voice UX patterns that depend on stable audio routing.
Ready to ship clean audio on Android?
Audio output switching on Android is a narrow problem with a wide surface. The API converged in 2022, the service and permission models tightened in 2024–2025, and LE Audio plus Android 17’s background-audio rules are the 2026 changes that shouldn’t surprise your team. Use setCommunicationDevice() as the default path, wrap it in a Kotlin StateFlow, keep the legacy SCO calls behind a version check, declare your foreground-service type, let Telecom own the route if you use Core-Telecom, and budget two to three weeks of real-device testing. The teams that ship a premium-feeling Android call app are the ones that treat this module as a first-class product surface, not an infrastructure detail.
If you’d rather not learn the OEM test matrix the hard way, Fora Soft has a reusable AudioDeviceManager and a standing device lab we can bring to your project. Our agent-engineering workflow shaves around a quarter off the calendar compared with a traditional build, which is usually the difference between making and missing a launch window.
The book · Volume 4 of 7
Audio for Video: A Complete Guide to Sound in Video Products: From PCM and Codecs to Loudness, Accessibility, and Real-Time Voice Processing
Routing audio to the right device is the first step; the voice that comes out still has to sound clean on a speakerphone, a headset or a hearing aid. Volume 4 of our handbook covers echo cancellation, automatic gain control, noise suppression and voice activity detection, plus channels, Opus and accessibility. It helps Android teams building calling apps. Written by Nikolay Sapunov, CEO at Fora Soft.
Read it on Amazon →Kindle & paperback · Fora Soft Video Engineering Handbook
Let’s scope your Android call app
Book a 30-minute scoping call: we’ll walk through your audio-routing requirements, OEM footprint and SDK choice, then sketch a timeline tailored to your release window.
.png)