Android Auto and Android Automotive OS help you bring your media app content to users in their car. A media app for cars must provide a media browser service so that Android Auto and Android Automotive OS (or another app with a media browser) can discover and display your content.
This guide assumes that you already have a media app that plays audio on a phone and that your media app conforms to the Android media app architecture.
This guide describes the required components of a MediaBrowserService
and
MediaSession
that your app needs in order to work on Android Auto or Android
Automotive OS. After you have completed the core media infrastructure, you can
add support for Android Auto and add support for
Android Automotive OS to your media
app.
Before you begin
- Review the Android media API documentation.
- Review the Android Automotive OS app design guidelines and Android Auto app design guidelines.
- Review the key terms and concepts listed in this section.
Key terms and concepts
- Media Browser Service
- An Android service implemented by your media app that complies with the
MediaBrowserServiceCompat
API. Your app uses this service to expose its content. - Media Browser
- An API used by media apps to discover media browser services and display their content. Android Auto and Android Automotive OS use a media browser to find your app's media browser service.
- Media Item
The media browser organizes its content in a tree of
MediaItem
objects. A media item can have either or both of these two flags:- Playable: This flag indicates that the item is a leaf on the content tree. The item represents a single sound stream such as a song on an album, a chapter in an audio book, or an episode of a podcast.
- Browsable: This flag indicates the item is a node on the content tree and it has children. For example, the item represents an album and its children are the songs on the album.
A media item that is both browsable and playable acts like a playlist. You can select the item itself to play all of its children, or you can browse its children.
- Vehicle-Optimized
An activity for an Android Automotive OS app that adheres to the Android Automotive OS design guidelines. The interface for these activities is not drawn by Android Automotive OS, so you must ensure that your app adheres to the design guidelines. Typically, this includes larger tap targets and font sizes, support for day and night modes, and higher contrast ratios.
Vehicle-optimized user interfaces are only allowed to be displayed when Car User Experience Restrictions (CUXRs) are not in effect because these interfaces can require extended attention or interaction from the user. CUXRs are not in effect when the car is stopped or parked but are always in effect when the car is in motion.
You don't need to design activities for Android Auto because Android Auto draws its own vehicle-optimized interface using the information from your media browser service.
Configure your app's manifest files
Before you can create your media browser service, you need to configure your app's manifest files.
Declare your media browser service
Both Android Auto and Android Automotive OS connect to your app through your media browser service in order to browse media items. You declare your Media Browse service in your manifest to allow Android Auto and Android Automotive OS to discover the service and connect to your app.
The following code snippet shows how to declare your media browser service in your manifest. You should include this code in the manifest file for your Android Automotive OS module and in the manifest file for your phone app.
<application>
...
<service android:name=".MyMediaBrowserService"
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
...
<application>
Specify an app icon
You need to specify an app icon that Android Auto and Android Automotive OS can use to represent your app in the system UI.
You can specify an icon that is used to represent your app using the following manifest declaration:
<!--The android:icon attribute is used by Android Automotive OS-->
<application
...
android:icon="@mipmap/ic_launcher">
...
<!--Used by Android Auto-->
<meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
android:resource="@drawable/ic_auto_icon" />
...
<application>
Create your media browser service
You create a media browser service by extending the MediaBrowserServiceCompat
class. Both Android Auto and Android Automotive OS can then use your service
to do the following:
- Browse your app's content hierarchy, in order to present a menu to the user.
- Get the token for your app's
MediaSessionCompat
object, in order to control audio playback.
Media browser service workflow
This section describes how Android Automotive OS and Android Auto interact with your media browser service during a typical user workflow.
- A user launches your app on Android Automotive OS or Android Auto.
- Android Automotive OS or Android Auto contacts your app's media browser
service using the
onCreate()
method. In your implementation of theonCreate()
method, you must create and register aMediaSessionCompat
object and its callback object. - Android Automotive OS or Android Auto calls your service's
onGetRoot()
method to get the root media item in your content hierarchy. The root media item is not displayed; instead, it's used to retrieve more content from your app. - Android Automotive OS or Android Auto calls your service's
onLoadChildren()
method to get the children of the root media item. Android Automotive OS and Android Auto display these media items as the top level of content items. Top-level content items should be browsable. - If the user selects a browsable media item, your service's
onLoadChildren()
method is called again to retrieve the children of the selected menu item. - If the user selects a playable media item, Android Automotive OS or Android Auto calls the appropriate media session callback method to perform that action.
- If supported by your app, the user can also search your content. In this
case, Android Automotive OS or Android Auto call your service's
onSearch()
method.
Build your content hierarchy
Android Auto and Android Automotive OS call your app's media browser service to
find out what content is available. You need to implement two methods in your
media browser service to support this: onGetRoot()
and
onLoadChildren()
.
Implement onGetRoot
Your service's onGetRoot()
method returns information about the root node of your content hierarchy.
Android Auto and Android Automotive OS use this root node to request the rest of
your content using the
onLoadChildren()
method.
The following code snippet shows a simple implementation of the
onGetRoot()
method:
Kotlin
override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle? ): BrowserRoot? = // Verify that the specified package is allowed to access your // content! You'll need to write your own logic to do this. if (!isValid(clientPackageName, clientUid)) { // If the request comes from an untrusted package, return null. // No further calls will be made to other media browsing methods. null } else MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null)
Java
@Override public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { // Verify that the specified package is allowed to access your // content! You'll need to write your own logic to do this. if (!isValid(clientPackageName, clientUid)) { // If the request comes from an untrusted package, return null. // No further calls will be made to other media browsing methods. return null; } return new MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null); }
For a more detailed example of this method, see the onGetRoot()
method in the Universal Android Music Player sample app on GitHub.
Add package validation for onGetRoot()
When a call is made to your service's onGetRoot()
method, the calling package passes identifying information to your service. Your
service can use this information to decide if that package can access your
content. For example, you can restrict access to your app's content to a list of
approved packages by comparing the clientPackageName
to your whitelist and
verifying the certificate used to sign the package's APK. If the package can't
be verified, return null
to deny access to your content.
In order to provide system apps (such as Android Auto and Android Automotive OS)
with access to your content, your service should always return a non-null
BrowserRoot
when these system apps call the onGetRoot()
method.
The following code snippet shows how your service can validate that the calling package is a system app:
fun isKnownCaller(
callingPackage: String,
callingUid: Int
): Boolean {
...
val isCallerKnown = when {
// If the system is making the call, allow it.
callingUid == Process.SYSTEM_UID -> true
// If the app was signed by the same certificate as the platform
// itself, also allow it.
callerSignature == platformSignature -> true
// ... more cases
}
return isCallerKnown
}
This code snippet is an excerpt from the PackageValidator
class in the Universal Android Music Player sample app on GitHub. See that class
for a more detailed example of how to implement package validation for your
service's onGetRoot()
method.
Implement onLoadChildren()
After receiving your root node object, Android Auto and Android Automotive OS
build a top-level menu by calling onLoadChildren()
on the root node object to get its children. Client apps build submenus by
calling this same method using child node objects.
Each node in your content hierarchy is represented by a MediaBrowserCompat.MediaItem
object. Each of these media items is identified by a unique ID string. Client
apps treat these ID strings as opaque tokens. When a client app wants to browse
to a submenu, or play a media item, it passes the token. Your app is responsible
for associating the token with the appropriate media item.
Note: Android Auto and Android Automotive OS have strict limits on how many media items they can display in each level of the menu. These limits minimize distractions for drivers and help them operate your app with voice commands. For more information, see Browsing content details and Android Auto app drawer.
The following code snippet shows a simple implementation of onLoadChildren()
method:
Kotlin
override fun onLoadChildren( parentMediaId: String, result: Result<List<MediaBrowserCompat.MediaItem>> ) { // Assume for example that the music catalog is already loaded/cached. val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf() // Check if this is the root menu: if (MY_MEDIA_ROOT_ID == parentMediaId) { // build the MediaItem objects for the top level, // and put them in the mediaItems list } else { // examine the passed parentMediaId to see which submenu we're at, // and put the children of that menu in the mediaItems list } result.sendResult(mediaItems) }
Java
@Override public void onLoadChildren(final String parentMediaId, final Result<List<MediaBrowserCompat.MediaItem>> result) { // Assume for example that the music catalog is already loaded/cached. List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>(); // Check if this is the root menu: if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) { // build the MediaItem objects for the top level, // and put them in the mediaItems list } else { // examine the passed parentMediaId to see which submenu we're at, // and put the children of that menu in the mediaItems list } result.sendResult(mediaItems); }
For a complete example of this method, see the
onLoadChildren()
method in the Universal Android Music Player sample app on GitHub.
Apply content styles
After building your content hierarchy using browsable or playable items, you can apply content styles that determine how those items display in the car.
You can use the following content styles:
- List items
-
This content style prioritizes titles and metadata over images.
- Grid items
-
This content style prioritizes images over titles and metadata.
Set default content styles
You can set global defaults for how your media items are displayed by including
certain constants in the BrowserRoot
extras bundle of your service's
onGetRoot()
method. Android Auto and Android Automotive OS read the extras bundle associated
with each item in the browse tree and look for those constants to determine the
appropriate style.
Use the following code to declare these constants in your app:
Kotlin
/** Declares that ContentStyle is supported */ val CONTENT_STYLE_SUPPORTED = "android.media.browse.CONTENT_STYLE_SUPPORTED" /** * Bundle extra indicating the presentation hint for playable media items. */ val CONTENT_STYLE_PLAYABLE_HINT = "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT" /** * Bundle extra indicating the presentation hint for browsable media items. */ val CONTENT_STYLE_BROWSABLE_HINT = "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT" /** * Specifies the corresponding items should be presented as lists. */ val CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1 /** * Specifies that the corresponding items should be presented as grids. */ val CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2
Java
/** Declares that ContentStyle is supported */ public static final String CONTENT_STYLE_SUPPORTED =; "android.media.browse.CONTENT_STYLE_SUPPORTED"; /** * Bundle extra indicating the presentation hint for playable media items. */ public static final String CONTENT_STYLE_PLAYABLE_HINT = "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT"; /** * Bundle extra indicating the presentation hint for browsable media items. */ public static final String CONTENT_STYLE_BROWSABLE_HINT = "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT"; /** * Specifies the corresponding items should be presented as lists. */ public static final int CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1; /** * Specifies that the corresponding items should be presented as grids. */ public static final int CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2;
After you have declared these constants, include them in the extras bundle of
your service's onGetRoot()
method to set the default content style. The following code snippet shows how to
set the default content style for browsable items to grids and playable items to
lists:
Kotlin
@Nullable override fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putBoolean(CONTENT_STYLE_SUPPORTED, true) extras.putInt(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE) extras.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE) return BrowserRoot(ROOT_ID, extras) }
Java
@Nullable @Override public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putBoolean(CONTENT_STYLE_SUPPORTED, true); extras.putInt(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE); extras.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE); return new BrowserRoot(ROOT_ID, extras); }
Set per-item content styles
The Content Style API lets you override the default content style for any
browsable media item's children. To override the default, create an extras
bundle in the MediaDescription
of a media item.
The following code snippet shows how to create a browsable MediaItem
that
overrides the default content style:
Kotlin
private fun createBrowsableMediaItem( mediaId: String, folderName: String, iconUri: Uri ): MediaBrowser.MediaItem { val mediaDescriptionBuilder = MediaDescription.Builder() mediaDescriptionBuilder.setMediaId(mediaId) mediaDescriptionBuilder.setTitle(folderName) mediaDescriptionBuilder.setIconUri(iconUri) val extras = Bundle() extras.putInt(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE) extras.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE) mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE) }
Java
private MediaBrowser.MediaItem createBrowsableMediaItem(String mediaId, String folderName, Uri iconUri) { MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder(); mediaDescriptionBuilder.setMediaId(mediaId); mediaDescriptionBuilder.setTitle(folderName); mediaDescriptionBuilder.setIconUri(iconUri); Bundle extras = new Bundle(); extras.putInt(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE); extras.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE); }
Group items using title hints
To group related media items together, you use a per-item hint. Every media item
in a group needs to declare an extras bundle in their MediaDescription
that
uses an identical string. This string is used as the title of the group and can
be localized.
The following code snippet shows how to create a MediaItem
with a subgroup
heading of "Songs"
:
Kotlin
val EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT = "android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT" private fun createMediaItem( mediaId: String, folderName: String, iconUri: Uri ): MediaBrowser.MediaItem { val mediaDescriptionBuilder = MediaDescription.Builder() mediaDescriptionBuilder.setMediaId(mediaId) mediaDescriptionBuilder.setTitle(folderName) mediaDescriptionBuilder.setIconUri(iconUri) val extras = Bundle() extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs") mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/) }
Java
public static final String EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT = "android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT"; private MediaBrowser.MediaItem createMediaItem(String mediaId, String folderName, Uri iconUri) { MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder(); mediaDescriptionBuilder.setMediaId(mediaId); mediaDescriptionBuilder.setTitle(folderName); mediaDescriptionBuilder.setIconUri(iconUri); Bundle extras = new Bundle(); extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs"); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/); }
Your app must pass all of the media items that you want to group together as a contiguous block. For example, suppose that you wanted to display two groups of media items, "Songs" and "Albums" (in that order), and your app passed in five media items in the following order:
- Media item A with
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
- Media item B with
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")
- Media item C with
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
- Media item D with
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
- Media item E with
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")
Because the media items for the "Songs" group and "Albums" group are not kept together in contiguous blocks, Android Auto and Android Automotive OS would interpret this as the following four groups instead:
- Group 1 called "Songs" containing media item A
- Group 2 called "Albums" containing media item B
- Group 3 called "Songs" containing media items C and D
- Group 4 called "Albums" containing media item E
To display these items in two groups, your app would pass the apps in the following order instead:
- Media item A with
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
- Media item C with
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
- Media item D with
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
- Media item B with
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")
- Media item E with
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")
Display additional metadata indicators

Figure 3. Playback view with metadata identifying the song and artist
You can include additional metadata indicators to provide at-a-glance information for content in the media browser tree and during playback. Within the browse tree, Android Auto and Android Automotive OS read the extras associated with an item and looks for certain constants to determine which indicators to display. During media playback, Android Auto and Android Automotive OS read the metadata for the media session and look for certain constants to determine indicators to display.
Use the following code to declare metadata indicator constants in your app:
Kotlin
// Bundle extra indicating that a song contains explicit content. var EXTRA_IS_EXPLICIT = "android.media.IS_EXPLICIT" /** * Bundle extra indicating that a media item is available offline. * Same as MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS. */ var EXTRA_IS_DOWNLOADED = "android.media.extra.DOWNLOAD_STATUS" /** * Bundle extra value indicating that an item should show the corresponding * metadata. */ var EXTRA_METADATA_ENABLED_VALUE:Long = 1 /** * Bundle extra indicating the played state of long-form content (such as podcast * episodes or audiobooks). */ var EXTRA_PLAY_COMPLETION_STATE = "android.media.extra.PLAYBACK_STATUS" /** * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has * not been played at all. */ var STATUS_NOT_PLAYED = 0 /** * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has * been partially played (i.e. the current position is somewhere in the middle). */ var STATUS_PARTIALLY_PLAYED = 1 /** * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has * been completed. */ var STATUS_FULLY_PLAYED = 2
Java
// Bundle extra indicating that a song contains explicit content. String EXTRA_IS_EXPLICIT = "android.media.IS_EXPLICIT"; /** * Bundle extra indicating that a media item is available offline. * Same as MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS. */ String EXTRA_IS_DOWNLOADED = "android.media.extra.DOWNLOAD_STATUS"; /** * Bundle extra value indicating that an item should show the corresponding * metadata. */ long EXTRA_METADATA_ENABLED_VALUE = 1; /** * Bundle extra indicating the played state of long-form content (such as podcast * episodes or audiobooks). */ String EXTRA_PLAY_COMPLETION_STATE = "android.media.extra.PLAYBACK_STATUS"; /** * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has * not been played at all. */ int STATUS_NOT_PLAYED = 0; /** * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has * been partially played (i.e. the current position is somewhere in the middle). */ int STATUS_PARTIALLY_PLAYED = 1; /** * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has * been completed. */ int STATUS_FULLY_PLAYED = 2;
After you have declared these constants, you can use them to display metadata
indicators. To display indicators that appear while the user is browsing the
media browser tree, create an extras bundle that includes one or more of these
constants and pass that bundle to the MediaDescription.Builder.setExtras()
method.
The following code snippet shows how to display indicators for an explicit, partially-played media item:
Kotlin
val extras = Bundle() extras.putLong(EXTRA_IS_EXPLICIT, 1) extras.putInt(EXTRA_PLAY_COMPLETION_STATE, STATUS_PARTIALLY_PLAYED) val description = MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build() return MediaBrowserCompat.MediaItem(description, /* flags */)
Java
Bundle extras = new Bundle(); extras.putLong(EXTRA_IS_EXPLICIT, 1); extras.putInt(EXTRA_PLAY_COMPLETION_STATE, STATUS_PARTIALLY_PLAYED); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build(); return new MediaBrowserCompat.MediaItem(description, /* flags */);
To display indicators for a media item that is currently being played, you can
declare Long values for EXTRA_IS_EXPLICIT
or EXTRA_IS_DOWNLOADED
in the
MediaMetadata.Builder()
method of your mediaSession
. You cannot display the
EXTRA_PLAY_COMPLETION_STATE
indicator on the playback view.
The following code snippet shows how to indicate that the current song in the playback view is explicit and downloaded:
Kotlin
mediaSession.setMetadata( MediaMetadata.Builder() .putString( MediaMetadata.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( EXTRA_IS_EXPLICIT, EXTRA_METADATA_ENABLED_VALUE) .putLong( EXTRA_IS_DOWNLOADED, EXTRA_METADATA_ENABLED_VALUE) .build())
Java
mediaSession.setMetadata( new MediaMetadata.Builder() .putString( MediaMetadata.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( EXTRA_IS_EXPLICIT, EXTRA_METADATA_ENABLED_VALUE) .putLong( EXTRA_IS_DOWNLOADED, EXTRA_METADATA_ENABLED_VALUE) .build());
Display browsable search results
To help a user browse your content, your app can allow a user to browse a group of search results that are related to their search query whenever that user performs a voice search. Android Auto and Android Automotive OS display these results as a "Show more results" bar in the interface.
Figure 4. “More results” option for viewing related search results
To display browsable search results, you should create a constant and include
that constant in the extras bundle of your service's onGetRoot()
method.
The following code snippet shows how to enable support in the onGetRoot()
method:
Kotlin
// Bundle extra indicating that onSearch() is supported val EXTRA_MEDIA_SEARCH_SUPPORTED = "android.media.browse.SEARCH_SUPPORTED" @Nullable fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putBoolean(EXTRA_MEDIA_SEARCH_SUPPORTED, true) return BrowserRoot(ROOT_ID, extras) }
Java
public static final String EXTRA_MEDIA_SEARCH_SUPPORTED = "android.media.browse.SEARCH_SUPPORTED"; @Nullable @Override public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putBoolean(EXTRA_MEDIA_SEARCH_SUPPORTED, true); return new BrowserRoot(ROOT_ID, extras); }
To start providing search results, override the onSearch()
method in your media browser service. Android Auto and Android Automotive OS
forward a user's search terms to this method whenever a user invokes the “Show
more results” affordance. You can organize the search results from your
service's onSearch()
method using title items to make them
more browsable. For example, if your app plays music, you might organize search
results by “Album”, “Artist”, and “Songs”.
The following code snippet shows a simple implementation of the onSearch()
method:
Kotlin
fun onSearch(query: String, extras: Bundle) { // Detach from results to unblock the caller (if a search is expensive) result.detach() object:AsyncTask() { internal var searchResponse:ArrayList internal var succeeded = false protected fun doInBackground(vararg params:Void):Void { searchResponse = ArrayList() if (doSearch(query, extras, searchResponse)) { succeeded = true } return null } protected fun onPostExecute(param:Void) { if (succeeded) { // Sending an empty List informs the caller that there were no results. result.sendResult(searchResponse) } else { // This invokes onError() on the search callback result.sendResult(null) } return null } }.execute() } // Populates resultsToFill with search results. Returns true on success or false on error private fun doSearch( query: String, extras: Bundle, resultsToFill: ArrayList ): Boolean { // Implement this method }
Java
@Override public void onSearch(final String query, final Bundle extras, Result<ArrayList<MediaItem>> result) { // Detach from results to unblock the caller (if a search is expensive) result.detach(); new AsyncTask<Void, Void, Void>() { ArrayList<MediaItem> searchResponse; boolean succeeded = false; @Override protected Void doInBackground(Void... params) { searchResponse = new ArrayList<MediaItem>(); if (doSearch(query, extras, searchResponse)) { succeeded = true; } return null; } @Override protected void onPostExecute(Void param) { if (succeeded) { // Sending an empty List informs the caller that there were no results. result.sendResult(searchResponse); } else { // This invokes onError() on the search callback result.sendResult(null); } return null; } }.execute() } /** Populates resultsToFill with search results. Returns true on success or false on error */ private boolean doSearch(String query, Bundle extras, ArrayList<MediaItem> resultsToFill) { // Implement this method }
Enable playback control
Android Auto and Android Automotive OS send playback control commands through
your service's MediaSessionCompat
.
You must register a session and implement the associated callback methods.
Register a media session
In your media browser service's onCreate()
method, create a MediaSessionCompat
,
then register the media session by calling setSessionToken()
.
The following code snippet shows how to create and register a media session:
Kotlin
override fun onCreate() { super.onCreate() ... // Start a new MediaSession val session = MediaSessionCompat(this, "session tag").apply { // Set a callback object to handle play control requests, which // implements MediaSession.Callback setCallback(MyMediaSessionCallback()) } sessionToken = session.sessionToken ... }
Java
public void onCreate() { super.onCreate(); ... // Start a new MediaSession MediaSessionCompat session = new MediaSessionCompat(this, "session tag"); setSessionToken(session.getSessionToken()); // Set a callback object to handle play control requests, which // implements MediaSession.Callback session.setCallback(new MyMediaSessionCallback()); ... }
When you create the media session object, you set a callback object that is used
to handle playback control requests. You create this callback object by
providing an implementation of the MediaSessionCompat.Callback
class for your app. The next section discusses how to implement this object.
Implement play commands
When a user requests playback for a media item from your app, Android Automotive
OS and Android Auto use the MediaSessionCompat.Callback
class from your app's MediaSessionCompat
object that they obtained from your app's media browser service. When a user
wants to control content playback, such as pausing playback or skipping to the
next track, Android Auto and Android Automotive OS invoke one of the callback
object's methods.
To handle content playback, your app must extend the abstract MediaSessionCompat.Callback
class and implement the methods that your app supports.
You should implement all of the following callback methods that make sense for the type of content that your app offers:
onPrepare()
- Invoked when the media source is changed. Android Automotive OS also invokes this method immediately after booting. Your media app must implement this method.
onPlay()
- Invoked if the user chooses play without choosing a specific item. Your
app should play its default content. If playback was paused with
onPause()
, your app should resume playback.Note: Your app should not automatically start playing music when Android Automotive OS or Android Auto connect to your media browser service. For more information, see Set initial playback state.
onPlayFromMediaId()
- Invoked when the user chooses to play a specific item. The method is passed the ID that your media browser service assigned to the media item in your content hierarchy.
onPlayFromSearch()
- Invoked when the user chooses to play from a search query. The app should make an appropriate choice based on the search string that was passed in.
onPause()
- Invoked when the user chooses to pause playback.
onSkipToNext()
- Invoked when the user chooses to skip to the next item.
onSkipToPrevious()
- Invoked when the user chooses to skip to the previous item.
onStop()
- Invoked when the user chooses to stop playback.
Your app should override these methods to provide any desired functionality. You
don't need to implement a method if it is not supported by your app. For
example, if your app plays a live stream (such as a sports broadcast), the
onSkipToNext()
method would not make sense to implement, and you could use the default
implementation of onSkipToNext()
instead.
Your app does not need any special logic to play content through the car's speakers. When your app receives a request to play content, it should play audio the same way it would normally (for example, playing the content through a user's phone speakers or headphones). Android Auto and Android Automotive OS automatically send the audio content to the car's system to play over the car's speakers.
For more information about playing audio content, see Media playback, Managing audio playback, and ExoPlayer.
Set standard playback actions
Android Auto and Android Automotive OS display playback controls based on the
actions that are enabled in the PlaybackStateCompat
object.
By default, your app must support the following actions:
ACTION_PLAY
ACTION_PAUSE
ACTION_STOP
ACTION_SKIP_TO_PREVIOUS
ACTION_SKIP_TO_NEXT
ACTION_PLAY_FROM_MEDIA
ACTION_PLAY_FROM_SEARCH
In addition, you might want to create a play queue that can be displayed for
the user. To do this, you need to call the setQueue()
and setQueueTitle()
methods, enable the ACTION_SKIP_TO_QUEUE_ITEM
action, and define the callback onSkipToQueueItem()
.
Android Auto and Android Automotive OS display buttons for each enabled action, as well as the playback queue if you choose to create one.
Reserve unused space
Android Auto and Android Automotive OS reserve space in the UI for the
ACTION_SKIP_TO_PREVIOUS
and ACTION_SKIP_TO_NEXT
actions. In addition,
Android Auto reserves space for the playback queue. If your app does not support
one of these functions, Android Auto and Android Automotive OS use the space to
display any custom actions you create.
If you don't want to fill those spaces with custom actions, you can reserve
them so that Android Auto and Android Automotive OS will leave the space blank
whenever your app does not support the corresponding function. To do this, call
the setExtras()
method with an extras bundle that contains constants that correspond to each of
the reserved functions. Set each constant that you want to reserve space for to
true
.
The following code snippet shows the constants that you can use to reserve unused space:
Kotlin
// Use these extras to show the transport control buttons for the corresponding actions, // even when they are not enabled in the PlaybackState. private const val SLOT_RESERVATION_SKIP_TO_NEXT = "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT" private const val SLOT_RESERVATION_SKIP_TO_PREV = "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS" private const val SLOT_RESERVATION_QUEUE = "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE"
Java
// Use these extras to show the transport control buttons for the corresponding actions, // even when they are not enabled in the PlaybackState. private static final String SLOT_RESERVATION_SKIP_TO_NEXT = "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT"; private static final String SLOT_RESERVATION_SKIP_TO_PREV = "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS"; private static final String SLOT_RESERVATION_QUEUE = "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE";
Set initial PlaybackState
As Android Auto and Android Automotive OS communicate with your media browser
service, your media session communicates the status of content playback using
the PlaybackState
. Your app
should not automatically start playing music when Android Automotive OS or
Android Auto connect to your media browser service. Instead, rely on Android
Auto and Android Automotive OS to resume or start playback based on the car's
state or user actions.
To accomplish this, set the initial
PlaybackState
of your media
session to STATE_STOPPED
,
STATE_PAUSED
,
STATE_NONE
,
or STATE_ERROR
.
Add custom playback actions
You can add custom playback actions to display additional actions that your
media app supports. If space permits (and is not reserved), Android adds the
custom actions to the transport controls. Otherwise, the custom actions are
displayed in the overflow menu. Custom actions display in the order in which
they are added to the PlaybackState
.
You can add these actions using the addCustomAction()
method in the PlaybackStateCompat.Builder
class.
The following code snippet shows how to add a custom “Start a radio channel” action:
Kotlin
stateBuilder.addCustomAction( PlaybackStateCompat.CustomAction.Builder( CUSTOM_ACTION_START_RADIO_FROM_MEDIA, resources.getString(R.string.start_radio_from_media), startRadioFromMediaIcon ).run { setExtras(customActionExtras) build() } )
Java
stateBuilder.addCustomAction(new PlaybackStateCompat.CustomAction.Builder( CUSTOM_ACTION_START_RADIO_FROM_MEDIA, resources.getString(R.string.start_radio_from_media), startRadioFromMediaIcon) .setExtras(customActionExtras) .build());
For a more detailed example of this method, see the setCustomAction()
method in the Universal Android Music Player sample app on GitHub.
After creating your custom action, your media session can respond to the action
by overriding the onCustomAction()
method.
The following code snippet shows how your app could respond to a “Start a radio channel” action:
Kotlin
override fun onCustomAction(action: String, extras: Bundle?) { when(action) { CUSTOM_ACTION_START_RADIO_FROM_MEDIA -> { ... } } }
Java
@Override public void onCustomAction(@NonNull String action, Bundle extras) { if (CUSTOM_ACTION_START_RADIO_FROM_MEDIA.equals(action)) { ... } }
For a more detailed example of this method, see the onCustomAction
method in the Universal Android Music Player sample app on GitHub.
Icons for custom actions
Each custom action that you create requires an icon resource. Apps in cars can run on many different screen sizes and densities, so icons that you provide must be vector drawables. A vector drawable allows you to scale assets without losing the detail. A vector drawable also makes it easy to align edges and corners to pixel boundaries at smaller resolutions.
Provide alternative icon style for disabled actions
For cases when a custom action is unavailable for the current context, swap the custom action icon with an alternative icon that shows that the action is disabled.
Support voice actions
Your media app must support voice actions to help provide drivers with a safe and convenient experience that minimizes distractions. For example, if your app is already playing one media item, the user can say “Play Bohemian Rhapsody" to tell your app to play a different song without looking at or touching the car's display.
For a more detailed example on how to implement voice-enabled playback actions in your app, see The Google Assistant and media apps.
Implement distraction safeguards
Because a user's phone is connected to their car's speakers while using Android Auto, you must take additional precautions to help prevent driver distraction.
Detect car mode
Android Auto media apps must not start playing audio through the car speakers
unless the user consciously starts playback (for example, by pressing play in
your app). Even a user-scheduled alarm from your media app must not start
playing music through the car speakers. To fulfill this requirement, your app
should determine if the phone is in car mode
before playing any audio. Your app can check whether the phone is in car mode
by calling the getCurrentModeType()
method.
If the user's phone is in car mode, media apps that support alarms must do one of the following things:
- Disable the alarm.
- Play the alarm over
STREAM_ALARM
, and provide a UI on the phone screen to disable the alarm.
The following code snippet shows how to check whether an app is running in car mode:
Kotlin
fun isCarUiMode(c: Context): Boolean { val uiModeManager = c.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager return if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_CAR) { LogHelper.d(TAG, "Running in Car mode") true } else { LogHelper.d(TAG, "Running in a non-Car mode") false } }
Java
public static boolean isCarUiMode(Context c) { UiModeManager uiModeManager = (UiModeManager) c.getSystemService(Context.UI_MODE_SERVICE); if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) { LogHelper.d(TAG, "Running in Car mode"); return true; } else { LogHelper.d(TAG, "Running in a non-Car mode"); return false; } }
Handle media advertisements
By default, Android Auto displays a notification when the media metadata changes
during an audio playback session. When a media app switches from playing music
to running an advertisement, it is distracting (and unnecessary) to display a
notification to the user. To prevent Android Auto from displaying a notification
in this case, you must set the media metadata key
android.media.metadata.ADVERTISEMENT
to 1
, as shown in the following code
snippet:
Kotlin
const val EXTRA_METADATA_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT" ... override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) { MediaMetadataCompat.Builder().apply { // ... if (isAd(mediaId)) { putLong(EXTRA_METADATA_ADVERTISEMENT, 1) } // ... mediaSession.setMetadata(build()) } }
Java
public static final String EXTRA_METADATA_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT"; @Override public void onPlayFromMediaId(String mediaId, Bundle extras) { MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); // ... if (isAd(mediaId)) { builder.putLong(EXTRA_METADATA_ADVERTISEMENT, 1); } // ... mediaSession.setMetadata(builder.build()); }
Handle general errors
When the app experiences an error, you should set the playback state to STATE_ERROR
and provide an error message using the setErrorMessage()
method. Error messages must be user-facing and localized with the user's current
locale. Android Auto and Android Automotive OS can then display the error
message to the user.
For more information about error states, see Working with a media session: States and errors.
If an Android Auto user needs to open your phone app to resolve an error, your message should provide that information to the user. For example, your error message would say "Sign in to [your app name]" instead of "Please sign in".