Android Auto and Android Automotive OS (AAOS) call your app's media browser service to discover which content is available. To support this, you implement these two methods in your media browser service.
Implement onGetRoot
Your service's onGetRoot
method returns information about the root node
of your content hierarchy. Android Auto and AAOS use this root
node to request the rest of your content using the onLoadChildren
method. This code snippet shows an 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 detailed example of this method, see onGetRoot
in the Universal
Android Music Player sample app on GitHub.
Add package validation
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:
- Compare the
clientPackageName
to your allowlist. - Verify the certificate used to sign the APK for the package.
If the package can't be verified, return null
to deny access to your content.
To provide system apps, such as Android Auto and AAOS,
with access to your content, your service must return a non-null
BrowserRoot
when these system apps call the onGetRoot
method.
The signature of the AAOS system app varies according to the make and model of a car. Be sure to allow connections from all system apps to support AAOS.
This 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.
In addition to allowing system apps, you must allow Google Assistant connect to
your MediaBrowserService
. Google Assistant uses separate package names
for the phone, which includes Android Auto Android AAOS.
Implement onLoadChildren
After receiving your root node object, Android Auto and AAOS
build a top-level menu by calling onLoadChildren
on the root node object
to get its descendants. Client apps build submenus by calling this same method
using descendant 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.
This code snippet shows an implementation of onLoadChildren
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 descendants 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 descendants of that menu in the mediaItems list.
}
result.sendResult(mediaItems);
}
To view an example of this method, see the onLoadChildren
in the
Universal Android Music Player sample app on GitHub.
Structure the root menu
Android Auto and Android Automotive OS have specific constraints about the
structure of the root menu. These are communicated to the MediaBrowserService
through root hints, which can be read through the Bundle argument passed into
onGetRoot()
. When followed, these hints let the system display the root
content as navigational tabs. If you don't follow these hints, some root
content might be dropped or made less discoverable by the system.
Figure 1. Root content displayed as navigational tabs.
By applying these hints, the system displays the root content as navigational tabs. By not applying these hints, some root content might be dropped or made less discoverable. These hints are transmitted:
Limit on the number of root children: In most cases, expect this number to be four, meaning that only four (or fewer) tabs can be shown.
Supported flags on the root children: Expect this value to be
MediaItem#FLAG_BROWSABLE
, which means only browsable items (not playable items) can be shown as tabs.Limit on the number of custom browse actions: Check how many custom browse actions are supported.
Kotlin
import androidx.media.utils.MediaConstants
override fun onGetRoot(
clientPackageName: String,
clientUid: Int,
rootHints: Bundle
): BrowserRoot {
val maximumRootChildLimit = rootHints.getInt(
MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT,
/* defaultValue= */ 4)
val supportedRootChildFlags = rootHints.getInt(
MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS,
/* defaultValue= */ MediaItem.FLAG_BROWSABLE)
// Rest of method...
}
Java
import androidx.media.utils.MediaConstants;
// Later, in your MediaBrowserServiceCompat.
@Override
public BrowserRoot onGetRoot(
String clientPackageName, int clientUid, Bundle rootHints) {
int maximumRootChildLimit = rootHints.getInt(
MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT,
/* defaultValue= */ 4);
int supportedRootChildFlags = rootHints.getInt(
MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS,
/* defaultValue= */ MediaItem.FLAG_BROWSABLE);
// Rest of method...
}
You can choose to branch the logic for the structure of your content hierarchy
based on the values of these hints, particularly if your hierarchy varies among
MediaBrowser
integrations outside of Android Auto and AAOS.
For example, if you normally show a root playable item, you might want to nest it under a root browsable item instead due to the value of the supported flags hint.
Apart from root hints, use these guidelines to optimally render tabs:
Monochrome (preferably white) icons for each tab item
Short and meaningful labels for each tab item (short labels reduce the odds that labels might be truncated)