Implement radio

This page explains how to implement radio at the hardware and software levels.

System components

The broadcast radio stack includes the following components.

Broadcast Radio architecture
Figure 1. Broadcast Radio architecture

Radio reference app

For details about how to implement radio control, see Radio control implementation.

A sample Java radio app (packages/apps/Car/Radio) serves as a reference implementation. When the app service starts, it requests Radio Manager to open a Radio Tuner. Then, the app can send requests to the Radio Tuner, such as tuning to a specific radio station, frequency, or to seek the next available radio station. The app receives updates from the Radio Manager and Radio Tuner in Radio, such as current program information, radio program lists, configurations, and vendor-defined parameters. The reference Radio app only supports AM and FM radio. OEMs can modify or replace the Radio app as desired.

Radio Manager

When the app requests Radio Manager to open a tuner, the Radio Manager (frameworks/base/core/java/android/hardware/radio/RadioManager.java) requests the Broadcast Radio Service to open a Tuner session and then wraps the session in a Radio Tuner (frameworks/base/core/java/android/hardware/radio/RadioTuner.java), which is returned to the app. The Radio Tuner defines APIs (such as tune, step, and cancel) that can be called from radio apps and send requests to Broadcast Radio Service. Callback methods (RadioTuner.Callback) defined in Radio Tuner send updates about the broadcast radio HAL, such as current program information, program lists, and vendor-defined parameters, from the Broadcast Radio Service to apps.

Broadcast Radio Service

The Broadcast Radio Service (frameworks/base/services/core/java/com/android/server/broadcastradio) is the client service for Broadcast Radio HAL. The Broadcast Radio Service coordinates multiple Radio Managers with Broadcast Radio HALs. The Broadcast Radio Service supports HAL interface definition language (HIDL) and Android interface definition language (AIDL) broadcast radio HALs. The Broadcast Radio Service links to the AIDL HAL when any AIDL HAL service exists; otherwise, the service links to the HIDL HAL. The Broadcast Radio Service creates a Radio Module for each available HAL instance (such as AM, FM, and DAB).

Each Radio Manager can request the Broadcast Radio Service to create a tuner session on the corresponding Radio Module, based on the type of radio. Each tuner session can call methods, such as tune, step, and cancel (defined in HAL interfaces) to perform operations on the corresponding broadcast radio HAL instance. When one tuner session receives a callback from the HAL instance on a HAL update, such as current program info, program list, configuration flags and, vendor parameters, callbacks about the update are sent to all Radio Tuners linked to the same Radio Module.

Broadcast radio HAL

To learn more about the HIDL and AIDL interfaces of broadcast radio and the differences between the two, see Broadcast radio HAL interface.

Broadcast radio hardware abstraction layer

The following sections describe how to work with the hardware abstraction layer (HAL) to implement broadcast radio.

Broadcast radio HAL interface

The Broadcast radio HAL provides data structures and interfaces at the hardware level to implement broadcast radio, such as AM/FM and DAB radio.

HIDL 2.0 and AIDL interfaces

The broadcast radio HAL uses the interfaces described in the following sections.

Announcement listener

IAnnouncementListener is the callback interface for the announcement listener, which can be registered on broadcast radio HAL to receive announcements. The interface has the following methods:

IAnnouncementListener
Description: Called whenever the announcement list has changed.
HIDL 2.0 oneway onListUpdated(vec<Announcement> announcements)
AIDL oneway void onListUpdated(in Announcement[] announcements)
Close handle

ICloseHandle is the generic close handle to remove a callback that doesn't need an active interface.

ICloseHandle
Description: Close the handle.
HIDL 2.0 close()
AIDL void close()

Callback interface

ITunerCallback is the callback interface called by the broadcast radio HAL to send updates to HAL client service.

ITunerCallback
Description: Called by the HAL when a tuning operation (tune, seek (in AIDL) or scan (in HIDL) and step succeeds) fails asynchronously.
HIDL 2.0 oneway onCurrentProgramInfoChanged(ProgramInfo info)
AIDL void onCurrentProgramInfoChanged(in ProgramInfo info)
Description: Called when tune, seek (in AIDL) or scan (in HIDL), or step succeeds.
HIDL 2.0 oneway onTuneFailed(Result result, ProgramSelector selector)
AIDL void onTuneFailed(in Result result, in ProgramSelector selector)
Description: Called when tune, seek (in AIDL) or scan (in HIDL), or step succeeds.
HIDL 2.0 oneway onCurrentProgramInfoChanged(ProgramInfo info)
AIDL void onCurrentProgramInfoChanged(in ProgramInfo info)
Description: Called when the program list is updated; the size of each chunk should be limited to 500kiB.
HIDL 2.0 oneway onProgramListUpdated(ProgramListChunk chunk)
AIDL oneway onProgramListUpdated(ProgramListChunk chunk)
Description: Called when the antenna is connected or disconnected.
HIDL 2.0 oneway onAntennaStateChange(bool connected)
AIDL void onCurrentProgramInfoChanged(in ProgramInfo info)
Description: Called when the vendor-specific parameter values are updated internally in HAL (should not be invoked after calling setParameters by HAL client).
HIDL 2.0 oneway onParametersUpdated(vec<VendorKeyValue> parameters)
AIDL void onParametersUpdated(in VendorKeyValue[] parameters)
Description: New in AIDL. Called when the config flag is updated internally in the HAL (should not be invoked after calling setConfigFlag by HAL client).
HIDL 2.0 Not applicable.
AIDL void onConfigFlagUpdated(in ConfigFlag flag, in boolean value)

Primary broadcast radio HAL interface

IBroadcastRadio is the primary interface for the broadcast radio HAL. In the HIDL 2.0 HAL, use the ITunerSession interface to the tuner to call operations. However, at most one tuner is active at one time (provided each broadcast radio HAL instance only has one tuner chip). ITunerSession was removed from the AIDL interfaces and its interfaces moved to IBroadcastRadio.

IBroadcastRadio
Description: Get the description of a module and its capabilities.
HIDL 2.0 getProperties() generates (Properties properties)
AIDL Properties getProperties()
Description: Fetches the current or possible AM/FM region configuration.
HIDL 2.0 getAmFmRegionConfig(bool full) generates (Result result, AmFmRegionConfig config)
AIDL AmFmRegionConfig getAmFmRegionConfig(bool full)
Description: Fetches the current DAB region configuration.
HIDL 2.0 getDabRegionConfig() generates (Result result, vec<DabTableEntry> config)
AIDL DabTableEntry[] getDabRegionConfig()
Description: Gets an image from the radio module cache. In AIDL, the image size must be less than 1MB due to a hard limit on the binder transaction buffer.
HIDL 2.0 getImage(uint32_t id) generates (vec<uint8_t> image)
AIDL byte[] getImage(in int id)
Description: Registers the announcement listener.
HIDL 2.0 registerAnnouncementListener(vec<AnnouncementType> enabled,IAnnouncementListener listener) generates (Result result, ICloseHandle closeHandle)
AIDL ICloseHandle registerAnnouncementListener(in IAnnouncementListener listener, in AnnouncementType[] enabled)
Description:
  • HIDL HAL: When a new tuner session is opened, the old session must be terminated.
  • AIDL HAL: Since no tuner session is available, only the tuner callback needs to be set. If it exists, the old callback should be unset.
HIDL 2.0 openSession(ITunerCallback callback) generates (Result result, ITunerSession session)
AIDL void setTunerCallback(in ITunerCallback callback)
Description:
  • HIDL HAL: Closing a tuner session must not fail and must only be issued once.
  • AIDL HAL: There is no tuner and only the tuner callback needs to be unset.
HIDL 2.0 close()
AIDL unsetTunerCallback()
Description: Tunes to a specified program.
HIDL 2.0 tune(ProgramSelector program) generates (Result result)
AIDL void tune(in ProgramSelector program)
Description: Seeks the next valid program on the air. To avoid confusion in AIDL, scan is renamed to seek.
HIDL 2.0 scan(bool directionUp, bool skipSubChannel) generates (Result result)
AIDL void seek(in boolean directionUp, in boolean skipSubChannel)
Description: Steps to the adjacent channel, which may not be occupied by any program.
HIDL 2.0 step(bool directionUp) generates (Result result)
AIDL void step(in boolean directionUp)
Description: Cancels pending tune, scan (in HIDL) or seek (in AIDL), or step operations.
HIDL 2.0 cancel()
AIDL void cancel()
Description: Applies a filter to the program list and starts sending program list updates over onProgramListUpdated callback.
HIDL 2.0 startProgramListUpdates(ProgramFilter filter) generates (Result result)
AIDL void startProgramListUpdates(in ProgramFilter filter)
Description: Stops sending program list updates.
HIDL 2.0 stopProgramListUpdates()
AIDL void stopProgramListUpdates()
Description: Fetches the current setting of a given config flag.
HIDL 2.0 isConfigFlagSet(ConfigFlag flag) generates (Result result, bool value)
AIDL boolean isConfigFlagSet(in ConfigFlag flag)
Description: Sets the given config flag.
HIDL 2.0 setConfigFlag(ConfigFlag flag, bool value) generates (Result result)
AIDL void setConfigFlag(in ConfigFlag flag, boolean value)
Description: Sets vendor-specific parameter values.
HIDL 2.0 setParameters(vec<VendorKeyValue> parameters)

generates,

(vec<VendorKeyValue> results)
AIDL VendorKeyValue[] setParameters(in VendorKeyValue[] parameters)
Description: Retrieves vendor-specific parameter values.
HIDL 2.0 getParameters(vec<string> keys) generates (vec<VendorKeyValue> parameters)
AIDL VendorKeyValue[] getParameters(in String[] keys)

Interface clarifications

Asynchronous behavior

Since each tuning operation (for example, tune, scan (in HIDL) or seek (in AIDL), and step) might be time-consuming and the thread should not be blocked for a long time, the operation should schedule time-consuming operations to occur later and quickly return a status or result. In detail, each operation should:

  • Cancel all pending tuning operations.
  • Check if the operation can be processed based on the method inputs and the status of the tuner.
  • Schedule the tuning task and then return the Result (in HIDL) or status (in AIDL) immediately. If the Result or status is OK, the tuner callback tuneFailed or currentProgramInfoChanged must be called when the tuning task has failed (for example, due to a timeout) or is complete.

Similarly, startProgramListUpdates also schedules the time-consuming task of updating the program list to take place later and to quickly return a status or result. The method first cancels pending update requests and then schedules the updating task and quickly returns the result.

Race condition

Due to the asynchronous behavior of tuning operations (for example, tune, scan (in HIDL) or seek (in AIDL), and step), there exists a race condition between canceling the operation and the tuning operations. If cancel is called after the HAL completes a tuning operation and before the callback completes, the cancel can be ignored and the callback should complete and be received by the HAL client.

Similarly, if stopProgramListUpdates is called after the HAL completes a program list update and before the onCurrentProgramInfoChanged callback completes, stopProgramListUpdates can be ignored and the callback should complete.

Data size limit

Since there is a hard limit on the binder transaction buffer, the data limit for some interface methods passing data of a potentially large size are clarified in the AIDL HAL.

  • getImage requires the image returned less than 1 MB.
  • onProgramListUpdate requires each chunk to be less than 500kiB. Larger program lists must be split by the HAL implementation into multiple chunks and sent through multiple callbacks.

Changes in AIDL HAL data structures

In addition to changes in the interfaces, these changes have been applied to the data structures defined in broadcast radio AIDL HAL, which takes advantage of the AIDL.

  • Constant enum is removed in AIDL and defined as const int in IBroadcastRadio. Meanwhile, ANTENNA_DISCONNECTED_TIMEOUT_MS is renamed to ANTENNA_STATE_CHANGE_TIMEOUT_MS. A new const int TUNER_TIMEOUT_MS is added. All tune, seek, and step operations must be completed within this time.
  • Enum RDS and Deemphasis are removed in AIDL and defined as const int in AmFmRegionConfig. Correspondingly, both fmDeemphasis and fmRds in ProgramInfo are declared as int, a bit computation result of the respective flags. Meanwhile, D50 and D75 are renamed to DEEMPHASIS_D50 and DEEMPHASIS_D75, respectively.
  • Enum ProgramInfoFlags are removed in AIDL and defined as const int in ProgramInfo with a prefix FLAG_ added. Correspondingly, infoFlags in ProgramInfo is declared as int, a bit computation result of flags. TUNED is also renamed to FLAG_TUNABLE, to better describe its definition that the station can be tuned to.
  • In AmFmBandRange, scanSpacing is renamed to seekSpacing, since scan is renamed to seek in AIDL.
  • Since the concept of union is introduced in AIDL, MetadataKey and Metadata defined in HIDL HAL are no longer used. An AIDL union Metadata is defined in AIDL HAL. Each enum value previously in MetadataKey is now a field in Metadata with type of string or int, depending on their definitions.

Radio control implementation

Radio control implementation is based on MediaSession and 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 library in 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 app. 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, located in packages/apps/Car/Radio.

Detailed control specifications

The MediaSession (through MediaSession.Callback) interface provides control mechanisms for the currently playing radio program:

  • onPlay, 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."
  • onSkipToNext, 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.
MediaBrowserService tree structure
Figure 2. MediaBrowserService tree structure

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.

MediaSession

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 onPause.

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.

By design, 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 platforms.

Note: The radio app 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 app is tuned to a station from the Favorites list, the app 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 app handles these actions.

Error handling

TransportControls 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 state to STATE_ERROR 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 STATE_CONNECTING (in case of direct tune) or STATE_SKIPPING_TO_PREVIOUS or NEXT while the command is being executed.

The client should watch the PlaybackState 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.

Add and remove favorites

MediaSession has rating support, which can be used to control Favorites. onSetRating called with a rating of type RATING_HEART 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 onSetRating operation.

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.

MediaBrowser

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 MediaDescription object contains a tuner-specific field just as Bluetooth does with EXTRA_BT_FOLDER_TYPE. In the case of broadcast radio, this leads to defining the following new fields in the public API:

  • 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 MediaBrowser.MediaItem scheme:

    • Program name (RDS PS, DAB service name). MediaDescription.getTitle.
    • FM frequency. URI (see ProgramSelector) or MediaDescription.getTitle (if an entry is in the BROADCASTRADIO_FOLDER_TYPE_BAND folder).
    • Radio-specific identifiers (RDS PI, DAB SId). MediaDescription.getMediaUri parsed 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) differ between raw channels and programs found in most cases (except for FM without RDS), but are mostly the same between programs found and favorites (except, for example, when AF was 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 MediaItem (see MediaSession).

    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.

    To support such actions, some top-level directories have the FLAG_PLAYABLE flag set (along with FLAG_BROWSABLE for folders).

    Action Tunes to How to issue
    Play radio Any radio channel startService(ACTION_PLAY_BROADCASTRADIO)

    or

    playFromMediaId(MediaBrowser.getRoot())
    Play FM Any FM channel Play from the mediaId of the FM band.

    The determination of which program to tune to is up to the app. This is typically the most recently tuned to channel from the given list. For details on ACTION_PLAY_BROADCASTRADIO, see General play intents.

    Discovery and service connection

    PackageManager can directly find the MediaBrowserService serving broadcast radio tree. To do so, call resolveService with the ACTION_PLAY_BROADCASTRADIO intent (see General play intents) and MATCH_SYSTEM_ONLY flag. To find all services that serve radio (there may be more than one; for example, separate AM/FM and satellite), use queryIntentServices.

    The resolved service handles the android.media.browse.MediaBrowserService bind intent, too. This is verified with GTS.

    To connect to the selected MediaBrowserService, create MediaBrowser instance for a given service component and connect. After establishing the connection, a handle to MediaSession can be obtained via getSessionToken.

    The Radio app can restrict client packages allowed to connect in an onGetRoot implementation 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 app (for example, a radio app) is installed on a device without such source support, it would still advertise itself as handling the ACTION_PLAY_BROADCASTRADIO intent, 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:

    1. Discover the radio service (call resolveService for ACTION_PLAY_BROADCASTRADIO).
    2. Create MediaBrowser and then connect to it.
    3. Determine the presence of MediaItem with EXTRA_BCRADIO_FOLDER_TYPE extra.

    Note: In most cases, the client must scan all available MediaBrowser trees to detect all available sources for a given device.

    Band names

    Band list is represented by a set of top-level directories with a folder type tag set to BCRADIO_FOLDER_TYPE_BAND. Their 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:

    • AM
    • FM
    • DAB

    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 enumerate separate bands 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_BROADCASTRADIO
    • 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_BLUETOOTH
    • 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 playFromMediaId with 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.

    ProgramSelector

    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 ProgramSelector to tune to an analog or digital channel. ProgramSelector consists 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 to fit into the MediaBrowser- or 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:

    • broadcastradio://program/RDS_PI/1234?AMFM_FREQUENCY=88500&AMFM_FREQUENCY=103300
    • broadcastradio://program/AMFM_FREQUENCY/102100
    • broadcastradio://program/DAB_SID_EXT/14895264?RDS_PI=1234

    The authority part (AKA host) of program provides 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 IdentifierType and the value format is a decimal or hexadecimal (with 0x prefix) number.

    All vendor-specific identifiers are represented by the VENDOR_ prefix. For example, VENDOR_0 for VENDOR_START and VENDOR_1 for VENDOR_START plus 1. Such URIs are specific to the radio hardware on which they were generated and cannot be transferred 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 playFromMediaId and 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.Uri for 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 app 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 app.

    Audio CD

    Similar to Audio CD in that the app 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 PLAYABLE, not BROWSABLE plus PLAYABLE. If there is no disk in a given slot, the item would be neither PLAYABLE nor BROWSABLE (but each slot must always be present in the tree).

     Audio CD tree structure
    Figure 3. Audio CD tree structure

    These entries would be marked in a similar way that broadcast radio folders are; they would contain additional extra fields defined in the MediaDescription API:

    • EXTRA_CD_TRACK: For every MediaItem on 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.

    Auxiliary input

    The app 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 playFromMediaId request.

    AUX tree structure
    Figure 4. AUX tree structure

    Each AUX MediaItem entry would have an extra field EXTRA_AUX_PORT_NAME set 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 PLAYABLE.

    Extra fields

    Define the following fields:

    • 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_CD_DISK or EXTRA_AUX_PORT_NAME extra field set.

    Detailed examples

    The following examples address the MediaBrowser tree structure for source types that are part of this design.

    Broadcast radio MediaBrowserService (handles ACTION_PLAY_BROADCASTRADIO):

    • Stations (browsable)EXTRA_BCRADIO_FOLDER_TYPE=BCRADIO_FOLDER_TYPE_PROGRAMS
      • BBC One (playable) URI: broadcastradio://program/RDS_PI/1234?AMFM_FREQUENCY=90500
      • ABC 88.1 (playable) URI: broadcastradio://program/RDS_PI/5678?AMFM_FREQUENCY=88100
      • ABC 88.1 HD1 (playable) URI: broadcastradio://program/HD_STATION_ID_EXT/158241DEADBEEF?AMFM_FREQUENCY=88100&RDS_PI=5678
      • ABC 88.1 HD2 (playable) URI: broadcastradio://program/HD_STATION_ID_EXT/158242DEADBEFE
      • 90.5 FM (playable) - FM without RDSURI: broadcastradio://program/AMFM_FREQUENCY/90500
      • 620 AM (playable) URI: broadcastradio://program/AMFM_FREQUENCY/620
      • BBC One (playable) URI: broadcastradio://program/DAB_SID_EXT/1E24102?RDS_PI=1234
    • Favorites (browsable, playable)EXTRA_BCRADIO_FOLDER_TYPE=BCRADIO_FOLDER_TYPE_FAVORITES
      • BBC One (playable) URI: broadcastradio://program/RDS_PI/1234?AMFM_FREQUENCY=101300
      • BBC Two (not playable)URI: broadcastradio://program/RDS_PI/1300?AMFM_FREQUENCY=102100
    • AM (browsable, playable): EXTRA_BCRADIO_FOLDER_TYPE=BCRADIO_FOLDER_TYPE_BANDEXTRA_BCRADIO_BAND_NAME_EN="AM"
      • 530 AM (playable) URI: broadcastradio://program/AMFM_FREQUENCY/530
      • 540 AM (playable) URI: broadcastradio://program/AMFM_FREQUENCY/540
      • 550 AM (playable) URI: broadcastradio://program/AMFM_FREQUENCY/550
    • FM (browsable, playable): EXTRA_BCRADIO_FOLDER_TYPE=BCRADIO_FOLDER_TYPE_BANDEXTRA_BCRADIO_BAND_NAME_EN="FM"
      • 87.7 FM (playable) URI: broadcastradio://program/AMFM_FREQUENCY/87700
      • 87.9 FM (playable) URI: broadcastradio://program/AMFM_FREQUENCY/87900
      • 88.1 FM (playable) URI: broadcastradio://program/AMFM_FREQUENCY/88100
    • DAB (playable): EXTRA_BCRADIO_FOLDER_TYPE=BCRADIO_FOLDER_TYPE_BANDEXTRA_BCRADIO_BAND_NAME_EN="DAB"

    Audio CD MediaBrowserService (handles ACTION_PLAY_AUDIOCD):

    • Disc 1 (playable) EXTRA_CD_DISK=1
    • Disc 2 (browsable, playable) EXTRA_CD_DISK=2
      • Track 1 (playable) EXTRA_CD_TRACK=1
      • Track 2 (playable) EXTRA_CD_TRACK=2
    • My music CD (browsable, playable) EXTRA_CD_DISK=3
      • All By Myself (playable) EXTRA_CD_TRACK=1
      • Reise, Reise (playable) EXTRA_CD_TRACK=2
    • Empty slot 4 (not playable) EXTRA_CD_DISK=4

    AUX MediaBrowserService (handles ACTION_PLAY_AUX):

    • AUX front (playable) EXTRA_AUX_PORT_NAME="front"
    • AUX rear (playable) EXTRA_AUX_PORT_NAME="rear"