Skip to content

Audio

Audio focus, ducking and interruptions

Who gets to be heard, what happens to everyone else, and why the sounds that matter most are the ones that must never be interrupted.

Intermediate4 minAudio · Focus · Ducking

Focus is Android's answer to "two things want to make noise". In a car the stakes are higher: a collision warning must be audible immediately, and it must never be the thing that gets ducked.

The focus model#

An app requests focus, stating what it wants and for how long. The framework grants or denies it and notifies whoever was displaced.

Request typeMeaningEffect on the current holder
AUDIOFOCUS_GAINI am taking over indefinitelyStop
AUDIOFOCUS_GAIN_TRANSIENTBrief interruptionPause
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCKBrief, we can coexistLower volume
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVEBrief, do not duck — silence othersPause

And what the displaced app receives:

Loss typeCorrect response
AUDIOFOCUS_LOSSStop, release resources, abandon focus
AUDIOFOCUS_LOSS_TRANSIENTPause; resume on regain
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKLower volume, keep playing

Requesting focus properly#

A media player that behaves
class MediaFocusHandler(context: Context, private val player: Player) {
 
    private val audioManager =
        context.getSystemService(AudioManager::class.java)
 
    private val attrs = AudioAttributes.Builder()
        .setUsage(AudioAttributes.USAGE_MEDIA)
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .build()
 
    private var resumeOnGain = false
 
    private val request = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
        .setAudioAttributes(attrs)
        // Tell the framework we can handle ducking ourselves. Without this the
        // platform ducks us automatically, which is fine for music but wrong
        // for speech.
        .setWillPauseWhenDucked(false)
        .setAcceptsDelayedFocusGain(true)
        .setOnAudioFocusChangeListener { change ->
            when (change) {
                AudioManager.AUDIOFOCUS_GAIN -> {
                    player.setVolume(1.0f)
                    if (resumeOnGain) { player.play(); resumeOnGain = false }
                }
                AudioManager.AUDIOFOCUS_LOSS -> {
                    resumeOnGain = false
                    player.stop()
                    abandon()
                }
                AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
                    resumeOnGain = player.isPlaying
                    player.pause()
                }
                AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
                    player.setVolume(0.2f)
                }
            }
        }
        .build()
 
    fun play() {
        when (audioManager.requestAudioFocus(request)) {
            AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> player.play()
            AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> { /* wait for GAIN */ }
            else -> { /* denied — do not play anyway */ }
        }
    }
 
    fun abandon() = audioManager.abandonAudioFocusRequest(request)
}

Never play without focus

requestAudioFocus returning AUDIOFOCUS_REQUEST_FAILED means something more important is happening — an emergency call, a safety chime. Playing regardless is how you end up talking over a collision warning. Handle the denial.

What the car changes#

Car audio focus is evaluated per zone and with an interaction matrix rather than simple last-request-wins.

For every pair of contexts, the platform decides:

  • Concurrent — both play, one may be ducked. Music + navigation.
  • Exclusive — the new one wins, the old one pauses. Music + phone call.
  • Rejected — the request is denied. Almost anything vs an emergency chime.

That third row is the one phone Android does not have. Some sounds cannot be interrupted, so requests that would interrupt them are refused outright rather than queued.

Watching focus decisions
adb shell dumpsys car_service --services CarAudioService | grep -A 30 -i focus
adb logcat -b all | grep -i CarAudioFocus

The CarAudioService dump lists current focus holders per zone. When "my audio stopped and I do not know why", this is the answer.

Ducking: who does it#

Two mechanisms, and knowing which your platform uses matters.

Software ducking — the app lowers its own volume on LOSS_TRANSIENT_CAN_DUCK, or the framework does it if the app opted out. Simple, works everywhere, and every app must implement it correctly.

Hardware ducking — the external amplifier attenuates the media bus when the navigation bus becomes active. Nothing in software changes. Lower latency, consistent across apps, and it requires the multi-bus routing from the previous topic.

Most production vehicles use hardware ducking for the navigation-over-media case precisely because it does not depend on every app behaving.

Sounds that must never be ducked#

Safety chimes, collision warnings, seatbelt reminders. These use USAGE_EMERGENCY, USAGE_SAFETY or USAGE_ASSISTANCE_SONIFICATION, are routed to their own bus in their own volume group, and the interaction matrix rejects requests that would displace them.

Chimes are usually not Android's job at all

On most production vehicles, critical warning sounds are generated by a separate ECU or the amplifier itself, not by Android. If the head unit crashes, the seatbelt chime must still work. Any design where a safety sound depends on Android being alive will not survive a safety review.

Testing focus#

The interaction cases worth automating
@Test fun navigationDucksMusicRatherThanPausingIt() {
    startMedia()
    requestFocus(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, GAIN_TRANSIENT_MAY_DUCK)
 
    assertThat(mediaPlayer.isPlaying).isTrue()
    assertThat(mediaPlayer.volume).isLessThan(0.5f)
}
 
@Test fun callPausesMedia() {
    startMedia()
    requestFocus(USAGE_VOICE_COMMUNICATION, GAIN_TRANSIENT)
 
    assertThat(mediaPlayer.isPlaying).isFalse()
}
 
@Test fun mediaResumesAfterTransientLoss() {
    startMedia()
    val call = requestFocus(USAGE_VOICE_COMMUNICATION, GAIN_TRANSIENT)
    abandonFocus(call)
 
    assertThat(mediaPlayer.isPlaying).isTrue()
}

The third test is the one that catches real bugs. Resume-after-interruption is where most media apps fail, and the driver notices immediately — the music simply never comes back after a phone call.

Next#

Power management, and why a head unit is never really off.

References & further reading

Code links target the main branch on cs.android.com. AOSP moves — if a path 404s, search the symbol instead.