Android Automotive OS and Android Auto help you bring your media app content to users in their car. For an overview of how Android enables app experiences on the road, see Android for Cars overview.
This guide assumes that you already have a media phone app. This guide describes how to build an app for Android Automotive OS and how to extend your phone app for Android Auto.
Before you begin
Before you begin building your app, make sure to follow the steps in Get started with Android for Cars, and then review the information in this section.
Key terms and concepts
- Media Browse Service
- An Android service implemented by your media app that complies with the
MediaBrowseServiceCompatAPI. Your app uses this service to expose Media Browse content to Android Automotive OS and Android Auto. - Media Browse
- An API used by media apps to expose their content to Android Automotive OS and Android Auto.
- Media Item
- An individual
MediaBrowserCompat.MediaItemobject in the Media Browse tree. Media items can be one of these types:- Playable: These items represent actual sound streams such as songs from an album, chapters from a book, or episodes from a podcast.
- Browseable: These items organize playable media items into groups. For example, you can group chapters into a book, songs into an album, or episodes into a podcast.
Note: A media item that is both browsable and playable is treated as playable.
- 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 Browse service.
Configure your app's manifest files
You need to configure your app's manifest files to indicate that your app is available for Android Automotive OS and that your phone app supports media services for Android Auto.
Declare support for Android Automotive OS
The app that you distribute to Android Automotive OS must be separate from your phone app. We recommend using modules and an Android App Bundle to help you reuse code and build and release your app with ease. Add the following entry into the manifest file in your Android Automotive OS module to indicate that the module's code is restricted to Android Automotive OS:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.media">
<uses-feature
android:name="android.hardware.type.automotive"
android:required="true"/>
</manifest>
Declare media support for Android Auto
Android Auto uses your phone app to provide a driver-optimized experience for a user. Use the following manifest entry to declare that your phone app supports Android Auto:
<application>
...
<meta-data android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc"/>
...
<application>
This manifest entry refers to an XML file that declares what automotive
capabilities your app supports. To indicate that you have a media app, add an
XML file named automotive_app_desc.xml to the res/xml/ directory in your
project. This file should include the following content:
<automotiveApp>
<uses name="media"/>
</automotiveApp>
Declare your Media Browse service
Both Android Automotive OS and Android Auto connect to your app through a media browser service in order to browse media items. You declare your Media Browse service in your manifest to allow Android Automotive OS and Android Auto to discover the service and connect to your app.
The following code snippet shows how to declare your Media Browse 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
Android Automotive OS and Android Auto display your app's icon to users in different places as they use your app and interact with their car. For example, if a user has a navigation app running, and one song finishes and a new song starts, the user would see a notification that includes your app's icon. Android Auto and Android Automotive OS also display your app's icon in other locations as the user browses your media content.
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>
Build a Media Browse service
You create a Media Browse service by extending the MediaBrowserServiceCompat
class. Both Android Automotive OS and Android Auto 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
MediaSessionCompatobject, in order to control audio playback.
Media browser service workflow
This section describes how Android Automotive OS and Android Auto interact with your Media Browse 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 Browse
service using the
onCreate()method. In your implementation of theonCreate()method, you must create and register aMediaSessionCompatobject 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 browseable. - If the user selects a browseable 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.
How users browse within media apps
To help users quickly browse your app's content, Android Auto includes a browsing capability that lets users select a letter from an on-screen keyboard. The user is then presented with a list of items beginning with that letter in the current drawer list. This works on both sorted and unsorted content and is currently available only in English.
Figure 1. Alpha picker on car screen
Figure 2. Alphabetical list view on car screen
Build your content hierarchy
Android Automotive OS and Android Auto call your app's Media Browse service to find out
what content is available. You need to implement two methods in your 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 Automotive OS and Android Auto 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 Automotive OS and Android Auto)
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 Automotive OS and Android Auto
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 Automotive OS and Android Auto 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 browseable 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.
- Title items
-
This content style displays even more metadata than the List Items content style. To use this content style, additional metadata must be provided for each media item.
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 Automotive OS and Android Auto 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)
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);
return new MediaBrowser.MediaItem(
mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
}
Add title items
To present your media items as title items, you use a per-item content style
that groups the items together. Every media item in the 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.
Android Automotive OS and Android Auto don't sort items that are grouped in this way. You need to pass the media items together and in the order that you want them to be displayed.
For example, suppose that your app passed three media items in the following order:
- Media item 1 with
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs") - Media item 2 with
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums") - Media item 3 with
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
Android Automotive OS and Android Auto would not merge media item 1 and media item 3 into a group, "Songs". Instead, the media items would be kept separate.
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")
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");
return new MediaBrowser.MediaItem(
mediaDescriptionBuilder.build(), /* playable or browsable flag*/);
}
Display additional metadata indicators
Figure 3. Playback view with metadata indicators
You can include additional metadata indicators to provide at-a-glance information for content in the Media Browse tree and during playback. Within the browse tree, Android Automotive OS and Android Auto read the extras associated with an item and looks for certain constants to determine which indicators to display. During media playback, Android Automotive OS and Android Auto 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 Browse 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 browseable 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 Automotive OS and Android Auto display these results as a "Show more results" bar in the interface.
Figure 4. Show more results option on the car screen
To display browseable 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 Browse service. Android Automotive OS and Android Auto
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 browseable. 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 Automotive OS and Android Auto 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 Browse 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 Browse service. When a user
wants to control content playback, such as pausing playback or skipping to the
next track, Android Automotive OS and Android Auto 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 Browse 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 Browse 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 Automotive OS and Android Auto 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 Automotive OS and Android Auto display playback controls based on the
actions that are enabled in the PlaybackStateCompat
object.
By default, your app must support the following actions:
ACTION_PLAYACTION_PAUSEACTION_STOPACTION_SKIP_TO_PREVIOUSACTION_SKIP_TO_NEXTACTION_PLAY_FROM_MEDIAACTION_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 Automotive OS and Android Auto display buttons for each enabled action, as well as the playback queue if you choose to create one.
Reserve unused space
Android Automotive OS and Android Auto 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 Automotive OS and Android Auto 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 Automotive OS and Android Auto 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 Automotive OS and Android Auto communicate with your Media Browse
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 Browse service. Instead, rely on Android
Automotive OS and Android Auto 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 [item]" to tell your app to play a different item without looking at or touching the car's display.
Declare support for voice actions
The following code snippet shows how to declare support for voice actions in your app's manifest files. You should include this code in the manifest file for your Android Automotive OS module and in the manifest file for your phone app.
<activity>
<intent-filter>
<action android:name=
"android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<category android:name=
"android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Parse voice search queries
When a user searches for a specific media item, such as “Play jazz on
[your app name]” or “Listen to [song title]”, the onPlayFromSearch()
callback method receives the voice search results in the query parameter and an
extras bundle.
Your app can parse the voice search query and start playback by following these steps:
- Use the extras bundle and search query string returned from the voice search to filter results.
- Build a playback queue based on these results.
- Play the most relevant media item from the results.
The onPlayFromSearch()
method takes an extras parameter with more detailed information from the voice
search. These extras help you find the audio content in your app for playback.
If the search results are unable to provide this data, you can implement logic
to parse the raw search query and play the appropriate tracks based on the
query.
The following extras are supported in Android Automotive OS and Android Auto:
The following code snippet shows how to override the onPlayFromSearch()
method in your MediaSession.Callback
implementation to parse the voice search query and begin playback:
Kotlin
override fun onPlayFromSearch(query: String?, extras: Bundle?) {
if (query.isNullOrEmpty()) {
// The user provided generic string e.g. 'Play music'
// Build appropriate playlist queue
} else {
// Build a queue based on songs that match "query" or "extras" param
val mediaFocus: String? = extras?.getString(MediaStore.EXTRA_MEDIA_FOCUS)
if (mediaFocus == MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE) {
isArtistFocus = true
artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST)
} else if (mediaFocus == MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE) {
isAlbumFocus = true
album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM)
}
// Implement additional "extras" param filtering
}
// Implement your logic to retrieve the queue
var result: String? = when {
isArtistFocus -> artist?.also {
searchMusicByArtist(it)
}
isAlbumFocus -> album?.also {
searchMusicByAlbum(it)
}
else -> null
}
result = result ?: run {
// No focus found, search by query for song title
query?.also {
searchMusicBySongTitle(it)
}
}
if (result?.isNotEmpty() == true) {
// Immediately start playing from the beginning of the search results
// Implement your logic to start playing music
playMusic(result)
} else {
// Handle no queue found. Stop playing if the app
// is currently playing a song
}
}
Java
@Override
public void onPlayFromSearch(String query, Bundle extras) {
if (TextUtils.isEmpty(query)) {
// The user provided generic string e.g. 'Play music'
// Build appropriate playlist queue
} else {
// Build a queue based on songs that match "query" or "extras" param
String mediaFocus = extras.getString(MediaStore.EXTRA_MEDIA_FOCUS);
if (TextUtils.equals(mediaFocus,
MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE)) {
isArtistFocus = true;
artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST);
} else if (TextUtils.equals(mediaFocus,
MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE)) {
isAlbumFocus = true;
album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM);
}
// Implement additional "extras" param filtering
}
// Implement your logic to retrieve the queue
if (isArtistFocus) {
result = searchMusicByArtist(artist);
} else if (isAlbumFocus) {
result = searchMusicByAlbum(album);
}
if (result == null) {
// No focus found, search by query for song title
result = searchMusicBySongTitle(query);
}
if (result != null && !result.isEmpty()) {
// Immediately start playing from the beginning of the search results
// Implement your logic to start playing music
playMusic(result);
} else {
// Handle no queue found. Stop playing if the app
// is currently playing a song
}
}
For a more detailed example on how to implement voice search to play audio content in your app, see the Universal Media Player sample.
Handle empty queries
When a user says “Play music on [your app name]”, Android Automotive OS or
Android Auto attempts to launch your app and play audio by calling your app's onPlayFromSearch()
method. However, because the user did not say the name of media item, the onPlayFromSearch()
method receives an empty query parameter. In these cases, your app should
respond by immediately playing audio, such as a song from the most recent
playlist or a random queue.
Implement voice-enabled playback actions
To provide a hands-free experience while users drive and listen to media content, your app must allow users to control content playback with voice actions. When users speak commands such as “Next song”, “Pause music”, or “Resume music”, the system triggers the corresponding callback method where you implement the playback control action.
To provide voice-enabled playback actions, first enable the hardware controls
by setting these flags in your app's MediaSession
object:
Kotlin
session.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
)
Java
session.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
After setting the flags, implement the callback methods with the playback controls that you support in your app. Android Automotive OS and Android Auto support the following voice-enabled playback actions:
| Example phrase | Callback method |
|---|---|
| "Next song" | onSkipToNext() |
| "Previous song" | onSkipToPrevious() |
| "Pause music" | onPause() |
| "Stop music" | onStop() |
| "Resume music" | onPlay() |
For a more detailed example on how to implement voice-enabled playback actions in your app, see the Universal Media Player sample.
Implement settings and sign-in activities for Android Automotive OS
In addition to your media browser service, you can also provide vehicle-optimized Settings and Sign-in activities for your Android Automotive OS app. These activities allow you provide app functionality that isn't included in the Android Media APIs.
Add a Settings activity
You can add a vehicle-optimized Settings activity so that users can configure settings for your app in their car. Your settings activity can also provide other workflows, like signing in or out of a user's account or switching user accounts.
Settings activity workflows
Your Settings activity can provide the user with different workflows. The following image shows how a user interacts with your Settings activity using Android Automotive OS:
Figure 6. Diagram of workflows for a Settings activity
Declare a Settings activity
You must declare your Settings activity in your app's manifest file, as shown in the following code snippet:
<application>
...
<activity android:name=".AppSettingsActivity"
android:exported="true"
android:theme="@style/SettingsActivity"
android:label="@string/app_settings_activity_title">
<intent-filter>
<action android:name="android.intent.action.APPLICATION_PREFERENCES"/>
</intent-filter>
</activity>
...
<application>
Implement your Settings activity
When a user launches your app, Android Automotive OS detects the
Settings activity that you declared and displays an affordance. The user can tap
or select this affordance using their car's display to navigate to the activity.
Android Automotive OS sends the ACTION_APPLICATION_PREFERENCES
intent that tells your app to start your settings activity.
Add a Sign-in activity
If your app requires a user to sign in before they can use your app, you can add a vehicle-optimized Sign-in activity that handles signing in and out of your app. You can also add sign-in and sign-out workflows to a Settings activity, but you should use a dedicated Sign-in activity if your app cannot be used until a user signs in.
Sign-in activity workflow
The following image shows how a user interacts with your Sign-in activity using Android Automotive OS:
Figure 7. Diagram of workflows for a Sign-in activity
Require sign in at app start
To require a user to sign in using your Sign-in activity before they can use your app, your media browse service must do the following things:
- Set the media session's
PlaybackStatetoSTATE_ERRORusing thesetState()method. This tells Android Automotive OS that no other operations can be performed until the error has been resolved. - Set the media session's
PlaybackStateerror code toERROR_CODE_AUTHENTICATION_EXPIRED. This tells Android Automotive OS that the user needs to authenticate. - Set the media session's
PlaybackStateerror message using thesetErrorMessage()method. Because this error message is user-facing, the message must be localized for the user's current locale. Set the media session's
PlaybackStateextras using thesetExtras()method. Include the following two keys:android.media.extras.ERROR_RESOLUTION_ACTION_LABEL: A string that is displayed on the button that begins the sign-in workflow. Because this string is user-facing, it must be localized for the user's current locale.android.media.extras.ERROR_RESOLUTION_ACTION_INTENT: APendingIntentthat directs the user to your Sign-in activity when the user taps the button referred to by theandroid.media.extras.ERROR_RESOLUTION_ACTION_LABEL.
The following code snippet shows how your app can require the user to sign in before using your app:
Kotlin
val signInIntent = Intent(this, SignInActivity::class.java)
val signInActivityPendingIntent = PendingIntent.getActivity(this, 0,
signInIntent, 0)
val extras = Bundle().apply {
putString(
"android.media.extras.ERROR_RESOLUTION_ACTION_LABEL",
"Sign in"
)
putParcelable(
"android.media.extras.ERROR_RESOLUTION_ACTION_INTENT",
signInActivityPendingIntent
)
}
val playbackState = PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_ERROR, 0, 0f)
.setErrorMessage(
PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED,
"Authentication required"
)
.setExtras(extras)
.build()
mediaSession.setPlaybackState(playbackState)
Java
Intent signInIntent = new Intent(this, SignInActivity.class);
PendingIntent signInActivityPendingIntent = PendingIntent.getActivity(this, 0,
signInIntent, 0);
Bundle extras = new Bundle();
extras.putString(
"android.media.extras.ERROR_RESOLUTION_ACTION_LABEL",
"Sign in");
extras.putParcelable(
"android.media.extras.ERROR_RESOLUTION_ACTION_INTENT",
signInActivityPendingIntent);
PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_ERROR, 0, 0f)
.setErrorMessage(
PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED,
"Authentication required"
)
.setExtras(extras)
.build();
mediaSession.setPlaybackState(playbackState);
After the user has successfully authenticated, your app must set the PlaybackState
back to a state other than STATE_ERROR
and then take the user back to Android Automotive OS by calling the activity's finish()
method.
Implement your Sign-in activity
Google offers a variety of identity tools that you can use to help users sign in to your app in their cars. Some tools, such as Firebase Authentication, provide full-stack toolkits that can help you build customized authentication experiences. Other tools leverage a user's existing credentials or other technologies to help you build seamless sign-in experiences for users.
We recommend the following tools to help you build an easier sign-in experience for users that have previously signed in on another device:
- Google Sign-in: If you've already implemented Google Sign-in for other devices (such as your phone app), you should also implement Google Sign-in for your Android Automotive OS app to support existing Google Sign-in users.
- Autofill with Google: If users have opted into Autofill with Google on their other Android devices, their credentials are saved to the Google password manager. Then, when the user signs in to your Android Automotive OS app, Autofill with Google suggests relevant saved credentials. Using Autofill with Google requires no application development effort; however, application developers should optimize their apps for better quality results. Autofill with Google is supported by all devices running Android Oreo 8.0 (API level 26) or higher (including Android Automotive OS).
Handle sign-in protected actions
Some apps allow a user to access some actions anonymously, but require the user to sign in before they can perform other actions. For example, a user might be able to play music in an app before signing in, but they must sign in before they can skip a song.
In this case, when the user attempts to perform the restricted action (skipping a song), your app could suggest that user authenticate by issuing a non-fatal error. By using a non-fatal error, the system displays the message to the user without interrupting playback for the current media item. To implement non-fatal error handling, complete the following steps:
- Set the
errorCodefor the media session'sPlaybackStatetoERROR_CODE_AUTHENTICATION_EXPIRED. This tells Android Automotive OS that user needs to authenticate. - Keep the
statefor the media session'sPlaybackStateas is—don't set it toSTATE_ERROR. This is what tells the system that the error is non-fatal. Set the media session's
PlaybackStateextras using thesetExtras()method. Include the following two keys:android.media.extras.ERROR_RESOLUTION_ACTION_LABEL: A string that is displayed on the button that begins the sign-in workflow. Because this string is user-facing, it must be localized for the user's current locale.android.media.extras.ERROR_RESOLUTION_ACTION_INTENT: APendingIntentthat directs the user to your Sign-in activity when the user taps the button referred to by theandroid.media.extras.ERROR_RESOLUTION_ACTION_LABEL.
Retain the rest of the media session's
PlaybackStatestate as is. This allows playback for the current media item to continue while the user decides whether to sign in or not.
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 Automotive OS and Android Auto 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".

