Seamless and timely switching between the sound output devices on Android is a feature that is usually taken for granted, but the lack of it (or problems with it) is very annoying. Today we will analyze how to implement such switching in Android ringtones, starting from the manual switching by the user to the automatic switching when headsets are connected. At the same time, let’s talk about pausing the rest of the audio system for the duration of the call. This implementation is suitable for almost all calling applications since it operates at the system level rather than the call engine level, e.g., WebRTC.
Audio output device management
All management of Android sound output devices is implemented through the system’s `AudioManager`. To work with it you need to add permission to `AndroidManifest.xml`:
First of all, when a call starts in our app, it is highly recommended to capture the audio focus — let the system know that the user is now communicating with someone, and it is best not to be distracted by sounds from other apps. For example, if the user was listening to music, but received a call and answered — the music will be paused for the duration of the call.
There are two mechanisms of audio focus request — the old one is deprecated, and the new one is available since Android 8.0. We implement for all versions of the system:
It is important to specify the most appropriate `ContentType` and `Usage` — based on these, the system determines which of the custom volume settings to use (media volume or ringer volume) and what to do with the other audio sources (mute, pause, or allow to run as before).
Great, we’ve got audio focus. It is highly recommended to save the original AudioManager settings right away before changing anything – this will allow us to restore it to its previous state when the call is over. You should agree that it would be very inconvenient if one application’s volume control would affect all the others
Now we can start setting our defaults. It may depend on the type of call (usually audio calls are on “speakerphone” and video calls are on “speakerphone”), on the user settings in the application or just on the last used speakerphone. Our conditional app is a video app, so we’ll set up the speakerphone right away:
Great, we have applied the default settings. If the application design provides a button to toggle the speakerphone, we can now very easily implement its handling:
Monitoring the connection of headphones
We’ve learned how to implement hands-free switching, but what happens if you connect headphones? Nothing, because `audioManager.isSpeakerphoneOn` is still `true`! And the user, of course, expects that when headphones are plugged in, the sound will start playing through them. And vice versa — if we have a video call, then when we disconnect the headphones the sound should start playing through the speakerphone.
There is no way out, we have to monitor the connection of the headphones. Let me tell you right away, the connection of wired and Bluetooth headphones is tracked differently, so we have to implement two mechanisms at once. Let’s start with wired ones and put the logic in a separate class:
In our example, we use `StateFlow` to implement subscription to the connection state, but instead, we can implement, for example, `HeadsetStateProviderListener`
Now just initialize this class and observe the `isHeadsetPlugged` field, turning the speaker on or off when it changes:
Bluetooth headphones connection monitoring
Now we implement the same monitoring mechanism for such Android sound output devices as Bluetooth headphones:
Now we implement the same monitoring mechanism for Bluetooth headphones:
To work with Bluetooth, we need another permission. For Android 12 and above, you need to declare in the manifest file and request at runtime following permission:
For devices with Android 11 and below, you need to declare in the manifest:
And now to automatically turn on the speakerphone when no headset is connected, and vice versa when a new headset is connected:
Tidying up after ourselves.
When the call is over, the audio focus is no longer useful to us and we have to get rid of it. Let’s restore the settings we saved at the beginning:
And now let’s give away the focus. Again, the implementation depends on the system version:
In the app you can switch the sound output between three device types:
- earpiece or wired
- Bluetooth device
However you cannot switch between two Bluetooth devices. On Android 11 though, there’s now a feature to add the device switch to Notification. The switcher displays all available devices with the enabled volume control feature. So it will simply not show users the devices they can’t switch to from the one they’re currently using as an output.
To add the switcher, use the notif with the Notification.MediaStyle style with MediaSession connected to it:
But how does Spotify have that quick and easy device switcher?
Our reader has noticed that Spotify does have that feature where you can switch between any devices you need. We cannot know for sure how they do that. But what we assume is that most likely Spotify implemented audio devices switching with MediaRouter API. It is used for seamless data exchange between two devices.
Great, here we have implemented the perfect UX of switching between Android sound output devices in our app. The main advantage of this approach is that it is almost independent of the specific implementation of calls: in any case, the played audio will be controlled by `AudioManager’, and we control exactly at its level!