Last week, the ITC (International Trade Commission) ruled in favor of Sonos as part of an ongoing patent dispute with Google related to the Google Cast protocol's multi-speaker control. I've researched how Google is modifying behavior in Android to work around this issue, and what it means for developers and users.
For users who have an Android device and two or more smart speakers compatible with the Google Cast protocol, the Google Home app can be used to group those speakers together. Once grouped, users can play music on that speaker group by selecting it from the list of targets shown when tapping the Cast icon in a music streaming app that has implemented the Cast SDK. The Cast SDK is bundled with Google Play Services; hence it is not included in AOSP. However, as with many other Google SDKs, the Cast SDK makes extensive use of Android platform APIs.
Following the release of the fifth Android 12 beta on September 8, 2021, app developers discovered a regression in the behavior of music casting that was caused by an unannounced change to Android’s media session stack. This change prevented physical volume button events from being passed to remote media sessions. This meant that users could no longer easily change the volume of a connected smart speaker from their smartphone after exiting the app that’s casting audio. Disabling a feature that’s used by so many so frequently without notice left observers wondering what precipitated this change. Google typically warns developers before it rolls out changes that affect an existing API, but in this case, developers were the ones to discover the regression. In this post, we’ll analyze the changes that Google made to Android 12 that blocked (and later re-enabled) volume adjustment for remote media sessions.
How was remote volume adjustment broken (and then fixed)?
How it broke
A code change titled “do not pass the volume key event to remote sessions” was committed to AOSP in late July, and this change eventually made its way to Android with the fifth beta release of Android 12. This tweaked the getDefaultVolumeSession method of the MediaSessionStack class to check whether the active media session uses local playback. (Android defines “local” playback as playback that happens on-device, while “remote” playback is playback that happens on an external device.)
Previously, the getDefaultVolumeSession method only checked the playback state of the media session, regardless of whether it was local or remote. This platform change resulted in volume key events being dispatched only when the active media session used local playback. This, in turn, affected apps utilizing Google’s support library for media, which is how most apps handle requests to adjust the volume for remote media sessions.
This regression is easy to reproduce and affects all devices running Android 12 versions prior to release 26, which was uploaded to AOSP last week. As documented on the Google Issue Tracker, simply open a music streaming app that supports the Cast SDK, pick an external device to stream audio to, exit the app while music is playing, and then press either the volume up or volume down key on the device. The volume slider will appear, but the current volume of the remote media session will not be shown nor will it change. The logcat output shared by the developer who first reported the issue confirms that the MediaSessionService is not dispatching volume key events to apps.
D/MediaSessionService: Adjusting com.spotify.music/spotify-media-session (userId=0) by 0. flags=4116, suggestedStream=-2147483648, preferSuggestedStream=false
Android 12 Beta 5:
D/MediaSessionService: Adjusting suggestedStream=-2147483648 by 0. flags=4116, preferSuggestedStream=false, session=null
The (partial) fix is in
With Android 12.0.0 Release 26, Google tweaked Android’s volume adjustment rules once again. The patch in question introduces new logic to decide whether to allow volume adjustments for remote sessions. (Side note: This patch is already rolled out to Pixel devices as part of the monthly firmware update for January 2022.)
The previously mentioned getDefaultVolumeSession method in the MediaSessionStack class no longer checks whether the active media session uses local playback. Instead, it now checks the output of the canHandleVolumeKey method, which was added with this patch. The new canHandleVolumeKey method of the MediaSessionStack class returns ‘true’ if the media session can handle volume key events, and ‘false’ otherwise. The method returns true if one of the following conditions is met:
- The active media session uses local playback.
- The flag config_volumeAdjustmentForRemoteGroupSessions is set to true.
- The list of routing sessions for the app contains only a single route (apart from the system routing session).
If conditions #1 or #2 are met, then #3 is never checked. However, if both #1 and #2 fail, then #3 is checked.
(The new showForSession method of the VolumeDialogControllerImpl class follows similar logic to decide whether to show a slider for the volume level of the Cast session in the system volume panel.)
AOSP, by default, defines config_volumeAdjustmentForRemoteGroupSessions as true, so Android’s default behavior allows for adjusting the volume of remote media sessions regardless of how many routes there are in that session. In other words, it doesn’t matter if the user is attempting to adjust the volume of a single speaker or a group of speakers, the volume key can control either.
However, this is not what actually happens on Pixel phones with the latest Android update. Instead, Pixel phones are able to adjust the volume of a remote media session only if there is a single speaker in the route. This is because condition #2 fails on the Pixel, as Google set the config_volumeAdjustmentForRemoteGroupSessions flag to false on its own devices. I verified this on my own Pixel 4 running Android 12 build SQ1A.220105.002 by running the following shell command:
Since this value deviates from AOSP’s default value, I knew that Google had to be overriding the value using a Runtime Resource Overlay (RRO). To find out which overlay was responsible, I then ran the following command:
cmd overlay dump
This command lists all installed RROs and their ID maps. From this output, I learned that the config_volumeAdjustmentForRemoteGroupSessions flag was being overridden by the PixelConfigOverlayCommon RRO. This RRO is common to all Pixel devices, hence the name.
Why did Google disable adjusting the volume of speaker groups?
After the first patch that disabled passing volume key events for all remote media sessions was pushed to a public build last year, speculation ran rampant about the reasoning. A Google engineer hinted vaguely at a “legal issue,” but the engineer did not elaborate what that “legal issue” was about.
Following the recent decision handed down by the ITC, it has become clearer what “legal issue” the Google engineer was referring to. The ITC held that Google was infringing on five U.S. patents held by Sonos, one of which is U.S. Patent No. 8588949 for a “method and apparatus for adjusting volume levels in a multi-zone system.” I cannot describe how Android or Nest products are alleged to violate this patent, but in response to this decision, Google announced that users will “no longer be able to change [their] Speaker Group volume using [their] phone’s physical volume button.” In addition, Google says that users “will need to adjust each speaker [volume] individually instead of using the group volume controller” to adjust the volume of a speaker group. Both of these limitations imposed on users align with the platform-level changes Google made to Android 12, suggesting that Google tweaked Android’s behavior in response to the Sonos dispute.
While the first patch appears to have been more of a “band aid,” the second patch seems tailored to allow Google to ship Pixel phones without the ability to control the volume of remote speaker group sessions. But it also leaves an easy way for OEMs to ship their own devices with the feature enabled. All an OEM would have to do is build AOSP with GMS and leave the default value (‘true’) for the config_volumeAdjustmentForRemoteGroupSessions flag in place.
Google may address this matter differently in future versions of Android, however, the second Android 12L beta exhibits the same behavior seen in the latest Android 12 release.
How users can restore remote volume adjustments for speaker groups
Just as Google disabled this behavior via RRO, so too can savvy users reenable this behavior with an RRO of their own. Simply create a RRO with a higher priority than the PixelConfigOverlayCommon RRO and the config_volumeAdjustmentForRemoteGroupSessions flag set to true. Installing this RRO will require superuser access, however, as Android does not allow installing RROs that aren’t signed with the platform certificate.
Android 12’s Fabricated Overlay CLI can also be used to generate an overlay that overrides the flag, provided the caller has superuser access. However, since the patch for CVE-2021-39630 clears all overlays fabricated by the shell on boot and this flag is read by the framework, the fabricated overlay approach won’t work since the framework needs to be restarted.
In summary, Google patched Android’s media framework twice in what’s likely a response to its dispute with Sonos. The first patch prevented the volume key event from being passed to remote media sessions, removing the ability to use the volume keys to adjust the volume of a casting session once the user exits the app that’s casting. The second patch tweaked the volume adjustment rules to let the volume key event be passed to remote media sessions if the flag config_volumeAdjustmentForRemoteGroupSessions is set to ‘true’ or if there’s only one route in the list of routing sessions for the app. The config_volumeAdjustmentForRemoteGroupSessions flag is set to ‘true’ in AOSP, but it’s overridden to ‘false’ by a RRO on Pixel devices. OEMs can build AOSP with GMS and leave the aforementioned flag as ‘true’ to let users adjust the volume of remote speaker groups, while technically savvy users can create a RRO of their own to reinstate the previous behavior on Pixel devices.