Radio control implementation is based on
MediaBrowse, which enable Media and voice assistant apps to
control the radio. For more information, see
Build media apps for cars on developer.android.com.
A media browse tree implementation is provided in the car-broadcastradio-support
packages/apps/Car/libs. This library also contains extensions of
ProgramSelector to convert to and from URI. It is recommended that radio implementations
use this library to build the associated browse tree.
Media Source Switcher
To provide a seamless transition between radio and other apps displayed in media,
the car-media-common library contains classes that should be integrated into the radio
MediaAppSelectorWidgetcan be included in the XML for the radio app
(the icon and drop-down used in the reference media and radio apps):
<com.android.car.media.common.MediaAppSelectorWidget android:id="@+id/app_switch_container" android:layout_width="@dimen/app_switch_widget_width" android:layout_height="wrap_content" android:background="@drawable/app_item_background" android:gravity="center" />
This widget launches the
AppSelectionFragment, which displays a
list of media sources that can be switched to. If a UI other than that provided is desired,
you can create a custom widget to launch the
AppSelectionFragment when the
switcher should be displayed.
AppSelectionFragment newFragment = AppSelectionFragment.create(widget, packageName, fullScreen); newFragment.show(mActivity.getSupportFragmentManager(), null);
A sample implementation is provided in the reference radio app implementation,
Detailed Control Specs
interface provides control mechanisms for the currently playing radio program:
onStop. (Un)mute radio playback.
onPause. Time-shifted pause (if supported).
onPlayFromMediaId. Play any content from a top-level folder. For example, "Play FM" or "Play Radio."
onPlayFromUri. Play a specific frequency. For example, "Play 88.5 FM."
onSkipToPrevious. Tune to a next or previous station.
onSetRating. Add or remove to or from Favorites.
The MediaBrowser exposes a tunable MediaItem over three types of top-level directories:
- (Optional) Programs (stations). This mode is typically used by dual-tuner radios to indicate all available tuneable radio stations at the user's location.
- Favorites. Radio programs added to the Favorites list, some may be unavailable (out of reception range).
- Band channels. All physically possible channels in the current region (87.9, 88.1, 88.3, 88.5, 88.7, 88.9, 89.1 and so on). Every band has a separate top-level directory.
Each element in each of these folders (AM/FM/Programs) is a MediaItem with a URI that can be used with MediaSession to tune. Each top-level folder (AM/FM/Programs) is a MediaItem with a mediaId that can be used with MediaSession to trigger playback and is up to the discretion of the OEM. For example, "Play FM," "Play AM," and "Play Radio" are all non-specific radio queries that use a mediaId to send to the OEM radio app. It's up to the radio app to determine what to play from the generic request and the mediaId.
Given there is no concept of pausing a broadcast stream, the Play, Pause, and Stop actions do not always apply to radio. With radio, the Stop action is associated with muting the stream while Play is associated with removing the mute.
Some radio tuners (or apps) provide the ability to simulate a broadcast stream pause by
caching content and then playing it back later. In such cases, use
Playing from mediaId and URI actions is intended to tune to a station fetched from the MediaBrowser interface. The mediaId is an arbitrary string provided by the radio app to impose a unique (so a given ID points to only one item) and stable (so a given item has the same ID through the whole session) value with which to identify a given station. The URI will be of a well-defined schema. In short, a URI-ized form of ProgramSelector. While this preserves the uniquity attribute, it need not be stable, although it can change when the station moves to a different frequency.
onPlayFromSearch is not used. Is is the responsibility of the client
(companion app) to select a search result from the MediaBrowser tree. Moving
that responsibility to the radio app would increase complexity, require formal contracts on how
string queries should appear, and result in an uneven user experience on different hardware
Note: The radio application does not contain additional information that would be useful to search for a station name not exposed to the client through the MediaBrowser interface.
Skipping to the next or previous station depends on the current context:
- When an application is tuned to a station from the Favorites list, the application can move to the next station from the Favorites list.
- Listening to a station from the Program list may result in tuning to the next available station, sorted according to channel number.
- Listening to an arbitrary channel may result in tuning to the next physical channel, even when there is no broadcast signal.
The radio application handles these actions.
actions (Play, Stop, and Next) doesn't provide feedback as to whether the action
succeeds or not. The only way to indicate an error is to set the MediaSession
with an error message.
The radio app must handle those actions and either execute them or set an error state.
If executing the Play command is not immediate, the playback state should be changed to
(in case of direct tune) or
while the command is being executed.
The client should watch the
and verify that the session changed the current program to what was requested or
entered into the error state.
STATE_CONNECTING must not exceed
30s. However a direct tune to a given AM/FM frequency should perform much faster.
Adding and Removing Favorites
MediaSession has rating support, which can be used to control Favorites.
onSetRating called with a rating of type
adds or removes the currently tuned station to or from the Favorites list.
Contrary to legacy presets, this model assumes an unordered and unbounded Favorites
list, when each saved favorite was allocated to a numerical slot (typically, 1 to 6).
As a result, preset-based systems would be incompatible with
The limitation of the MediaSession API is that only the station currently tuned to can be added or removed. For example, items must be selected first before they can be removed. This is only a limitation of the MediaBrowser client, such as a companion app. The radio app is not similarly restricted. This portion is optional when an app doesn't support Favorites.
To express which frequencies or physical channel names (when tuning to an arbitrary channel is suitable for a given radio technology) are valid for a given region, all valid channels (frequencies) are listed for each band. In the US region, this amounts to 101 FM channels from in the range of 87.8 to 108.0 MHz range (using 0.2MHz spacing) and 117 AM channels in the range of 530 to 1700 kHz (using 10kHz spacing). Because HD radio uses the same channel space, it is not presented separately.
The list of currently available radio programs is flat in that this doesn't allow display schemes such as grouping by direct audio broadcast (DAB) ensemble.
Entries on the Favorite list may not be tunable. For example if a given program is out of range. The radio app may or may not detect if the entry can be tuned to beforehand. If so, it may not mark the entry as playable.
To identify top-level folders, the same mechanism used by Bluetooth is applied.
That is, an Extras bundle of the
object contains a tuner-specific field just as Bluetooth does with
In the case of broadcast radio, this leads to defining the following new fields in the
EXTRA_BCRADIO_FOLDER_TYPE = "android.media.extra.EXTRA_BCRADIO_FOLDER_TYPE". One of the following values:
BCRADIO_FOLDER_TYPE_PROGRAMS = 1. Currently available programs.
BCRADIO_FOLDER_TYPE_FAVORITES = 2. Favorites.
BCRADIO_FOLDER_TYPE_BAND = 3. All physical channels for a given band.
There is no need to define any radio-specific custom metadata fields, as all the relevant data fits into the existing
- Program name (RDS PS, DAB service name).
- FM frequency. URI (see ProgramSelector) or
MediaDescription.getTitle(if an entry is in the
- Radio-specific identifiers (RDS PI, DAB SId).
MediaDescription.getMediaUriparsed to ProgramSelector.
Typically, there is no need to fetch FM frequency for an entry on the current program or Favorites list (as the client should operate on media IDs). However, if such a need were to arise (for example, for display purposes), it's present in the URI and can be parsed to
ProgramSelector. That said, it's not recommended the URI be used to select items within the current session. For details, see ProgramSelector.
To avoid performance or binder-related issues, the MediaBrowser service must support pagination:
Note: By default, pagination is implemented by default in the
onLoadChildren()variant without options handling.
Related entries from all types of lists (raw channels, programs found and favorites) may have different mediaIds (it's up to the radio app; support library will have them different). The URIs (in ProgramSelector form) will be different between raw channels and programs found in most cases (except for FM without RDS); but mostly the same between programs found and favorites (except, for example, when AF got updated).
Having different mediaIds for entries from different types of lists makes it possible to take different actions on them. You can traverse either the Favorites list or the All Programs list on
onSkipToNext, depending on the folder of recently selected
Special tune actions
Program list enables users to tune to a specific station, but does not allow users to make general requests such as "tune to FM", which could result in tuning to a recently listened to station on the FM band.
Action Tunes to How to issue Play radio Any radio channel
Play FM Any FM channel Play from mediaId of FM band
The determination of which program to tune to is up to the application. This is typically the most recently tuned to channel from the given list. For details on
ACTION_PLAY_BROADCASTRADIO, see the General play intents.
Discovery and service connection
PackageManager can directly find the MediaBrowserService serving broadcast radio tree. To do so, call
ACTION_PLAY_BROADCASTRADIOintent (see General play intents) and
MATCH_SYSTEM_ONLYflag. To find all services that serve radio (there may be more than one, for example separate AM/FM and satellite), use
The resolved service will handle the
android.media.browse.MediaBrowserServicebind intent, too. This is verified with GTS.
To connect to the selected MediaBrowserService, create
MediaBrowserinstance for a given service component and
connect. After establishing the connection, a handle to MediaSession can be obtained via
The Radio app can restrict client packages allowed to connect in an
onGetRootimplementation of their service. The app should allow system apps to connect without whitelisting. For details about whitelisting, see Accept the Assistant app package and signature.
If the source-specific application (for example, a radio app) is installed on a device without such source support, it would still advertise itself as handling the
ACTION_PLAY_BROADCASTRADIOintent, but its MediaBrowser tree would not contain radio-specific tags. Thus, a client willing to check if a given source is available on a device, must:
- Discover the radio service (call
MediaBrowserfor it and then connect to it.
- Determine the presense of
Note: In most cases, the client must scan all available MediaBrowser trees to detect all available sources for a given device.
Band list is represented by a set of top-level directories with a folder type tag set to
MediaItem's titles are localized strings representing band names. In most cases it will be the same as English translation, but the client can't depend on that assumption.
To provide a stable mechanism for looking up certain bands, an extra tag is added for band folders:
EXTRA_BCRADIO_BAND_NAME_EN. This is a non-localized name of the band and can only take one of these predefined values:
If the band is not on this list, the band name tag should not be set. However, if the band is on the list, it must have a tag set. HD radio doesn't have separate bands enumerated as it uses the same underlying medium as AM/FM.
General play intents
Each app dedicated for playing given source (like radio or CD) must handle a general play intent, to start playing some content possibly from inactive state (for example, after boot). It's up to the app how to select content to play, but it's usually the recently played radio program or CD track.
There is a separate intent defined for each audio source:
android.car.intent.action.PLAY_AUDIOCD: CD-DA or CD-Text
android.car.intent.action.PLAY_DATADISC: optical data disc like CD/DVD, but not CD-DA (may be Mixed Mode CD)
android.car.intent.action.PLAY_AUX: without specifying which AUX port
android.car.intent.action.PLAY_USB: without specifying which USB device
android.car.intent.action.PLAY_LOCAL: local media storage (built-in flash)
Intents were chosen to be used for general play command, because they solve two problems at once: the general play command itself and service discovery. Additional benefit of having such intent would be a possibility to execute such simple action without opening MediaBrowser session.
Service discovery is actually the more important problem solved with these intents. Procedure for service discovery is easy and unequivocal this way (see Discovery and service connection).
To make some client implementations easier, there is an alternative way of issuing such Play command (that also has to be implemented by the radio app): issuing
playFromMediaIdwith the rootId of the root node (used as mediaId). While the root node is not meant to be playable, its rootId is an arbitrary string which can be made to be consumable as mediaId. However, clients are not required to understand this nuance.
While mediaId is enough to select a channel from the MediaBrowserService, it becomes bound to a session and not consistent between providers. In some cases the client may need an absolute pointer (such as an absolute frequency) to maintain it between sessions and devices.
In the era of digital radio broadcasts, a bare frequency is not sufficient to tune to a specific station. Therefore, use
ProgramSelectorto tune to an analog or digital channel.
ProgramSelectorconsists of two parts:
- Primary identifier. A unique and stable identifier for a given radio station that doesn't change but may not be enough to tune to that station. For example, RDS PI code, which may be translated to the call sign in the US.
- Secondary identifiers. Additional identifiers useful for tuning to that station (for example, frequency), possibly including identifiers from other radio technologies. For example, a DAB station may have an analog broadcasting fallback.
To enable ProgramSelector fit into the MediaBrowser/MediaSession-based solution, define a URI schema to serialize it. The schema is defined as follows:
broadcastradio://program/<primary ID type>/<primary ID>? <secondary ID type>=<secondary ID>&<secondary ID type>=<secondary ID>
In this example, the secondary Identifiers portion (after the question mark (
?)) is optional and can be removed to provide a stable identifier for use as
mediaId. For example:
The authority part (AKA host) of
programprovides some room for scheme extension in the future. The identifier type strings are precisely specified as their names in the HAL 2.x definition of
IdentifierTypeand the value format is a decimal or hexadecimal (with
All vendor-specific identifiers are represented by the
VENDOR_prefix. For example,
VENDOR_START + 1. Such URIs are specific to the radio hardware on which they were generated and cannot be transfered between devices made by different OEMs.
These URIs must be assigned to each MediaItem under the top-level radio folders. In addition, the MediaSession must support both
playFromUri. However, the URI is primarily intended for radio metadata extraction (such as FM frequency) and persistent storage. There is no guarantee the URI will be available for all media items (for example, when the primary ID type is not yet supported by the framework). On the other hand, Media ID always works. It is not recommended that clients use URI to select items from the current MediaBrowser session. Instead, use
playFromMediaId. That said, it is not optional for the serving app and missing URIs are reserved for well-justified cases.
The initial design used a single colon instead of the
://sequence after the scheme part. However, the former is not supported by
android.net.Urifor absolute hierarchical URI references.
Other source types
Other audio sources can be handled similarly. For example, auxiliary input and the Audio CD player.
A single application may serve multiple types of sources. In such cases, it's recommended you create a separate MediaBrowserService for each type of source. Even in a set-up with multiple served sources/MediaBrowserServices, it's strongly recommended to have a single MediaSession within a single application.
Similar to Audio CD in that the application that serves such disks would expose MediaBrowser with a single browsable entry (or more, if the system has a CD changer), which in turn would contain all tracks of a given CD. If the system does not have the knowledge about the tracks on every CD (for example, when all disks are inserted in a cartridge at once and it doesn't read them all), then MediaItem for the entire disk would be just
BROWSABLE+PLAYABLE. If there is no disk in a given slot, the item would be neither
BROWSABLE(but each slot must always be present in the tree).
These entries would be marked in a similar way that broadcast radio folders are – they would contain additional extra fields defined in MediaDescription API:
EXTRA_CD_TRACK: for every
MediaItemon Audio CD, 1-based track number.
EXTRA_CD_DISK: 1-based disk number.
For CD-Text enabled system and compatible disk, the top-level MediaItem would have a title of the disk. Similarly, the MediaItems for tracks, would have a title of the track.
The application that serves auxiliary input exposes a MediaBrowser tree with a single entry (or more, when multiple ports exist) representing the AUX in port. The respective MediaSession takes its mediaId and switches to that source after getting the
Each AUX MediaItem entry would have an extra field
EXTRA_AUX_PORT_NAMEset to the non-localized name of the port without the "AUX" phrase. For example, "AUX 1" would have be set to "1", "AUX front" to "front" and "AUX" to an empty string. In non-English locales, the name tag would remain the same English string. Unlikely as for
EXTRA_BCRADIO_BAND_NAME_EN, the values are OEM-defined and not constrained to a predefined list.
If the hardware can detect devices connected to the AUX port, the hardware should mark the MediaItem as
PLAYABLE, only if input is connected. The hardware should still be enumerated (but not
PLAYABLE) if nothing was connected to this port. If the hardware has no such capability, the MediaItem must always be set to
Therefore, the following extra keys must be defined:
EXTRA_CD_TRACK = "android.media.extra.CD_TRACK"
EXTRA_CD_DISK = "android.media.extra.CD_DISK"
EXTRA_AUX_PORT_NAME = "android.media.extra.AUX_PORT_NAME"
Client needs to review the top-level MediaItems for elements having the
EXTRA_AUX_PORT_NAMEextra field set.
The following examples address the MediaBrowser tree structure for source types that are part of this design.
Broadcast radio MediaBrowserService (handles
- Stations (browsable)
- BBC One (playable)
- ABC 88.1 (playable)
- ABC 88.1 HD1 (playable)
- ABC 88.1 HD2 (playable)
- 90.5 FM (playable) – FM without RDS
- 620 AM (playable)
- BBC One (playable)
- BBC One (playable)
- Favorites (browsable, playable)
- BBC One (playable)
- BBC Two (not playable)
- BBC One (playable)
- AM (browsable,
- 530 AM (playable)
- 540 AM (playable)
- 550 AM (playable)
- 530 AM (playable)
- FM (browsable, playable)
- 87.7 FM (playable)
- 87.9 FM (playable)
- 88.1 FM (playable)
- 87.7 FM (playable)
- DAB (playable)
Audio CD MediaBrowserService (handles
- Disc 1 (playable)
- Disc 2 (browsable, playable)
- Track 1 (playable)
- Track 2 (playable)
- Track 1 (playable)
- My music CD (browsable, playable)
- All By Myself (playable)
- Reise, Reise (playable)
- All By Myself (playable)
- Empty slot 4 (not playable)
AUX MediaBrowserService (handles
- AUX front (playable)
- AUX rear (playable)