Android Auto и Android Automotive OS помогут вам донести контент вашего мультимедийного приложения до пользователей в автомобиле. Медиа-приложение для автомобилей должно предоставлять службу медиа-браузера, чтобы Android Auto и ОС Android Automotive или другое приложение с медиа-браузером могли обнаруживать и отображать ваш контент.
В этом руководстве предполагается, что у вас уже есть мультимедийное приложение, воспроизводящее звук на телефоне, и что ваше мультимедийное приложение соответствует архитектуре мультимедийных приложений Android.
В этом руководстве описаны обязательные компоненты MediaBrowserService
и MediaSession
, необходимые вашему приложению для работы в Android Auto или Android Automotive OS. После завершения создания базовой медиа-инфраструктуры вы можете добавить поддержку Android Auto и ОС Android Automotive в свое мультимедийное приложение.
Прежде чем начать
- Ознакомьтесь с документацией по Android Media API .
- Ознакомьтесь с рекомендациями по созданию мультимедийных приложений .
- Ознакомьтесь с ключевыми терминами и понятиями, перечисленными в этом разделе.
Ключевые термины и понятия
- Служба медиабраузера
- Служба Android, реализованная вашим мультимедийным приложением и соответствующая API
MediaBrowserServiceCompat
. Ваше приложение использует этот сервис для предоставления своего контента. - Медиа-браузер
- API, используемый мультимедийными приложениями для обнаружения служб медиа-браузера и отображения их содержимого. Android Auto и Android Automotive OS используют медиабраузер для поиска службы медиабраузера вашего приложения.
- Медиа-элемент
Медиабраузер организует свое содержимое в виде дерева объектов
MediaItem
. Медиа-элемент может иметь один или оба следующих флага:-
FLAG_PLAYABLE
: указывает, что элемент является листом дерева содержимого. Этот элемент представляет собой один звуковой поток, например песню в альбоме, главу аудиокниги или эпизод подкаста. -
FLAG_BROWSABLE
: указывает, что элемент является узлом в дереве контента и имеет дочерние элементы. Например, элемент представляет альбом, а его дочерние элементы — это песни в альбоме.
Медиа-элемент, который доступен как для просмотра, так и для воспроизведения, действует как список воспроизведения. Вы можете выбрать сам элемент для воспроизведения всех его дочерних элементов или просмотреть его дочерние элементы.
-
- Оптимизирован для транспортных средств
Действие для приложения Android Automotive OS, которое соответствует рекомендациям по проектированию Android Automotive OS . Интерфейс для этих действий не создается ОС Android Automotive, поэтому вы должны убедиться, что ваше приложение соответствует рекомендациям по проектированию. Обычно это включает в себя более крупные объекты касания и размеры шрифта, поддержку дневного и ночного режимов и более высокий коэффициент контрастности.
Пользовательские интерфейсы, оптимизированные для транспортных средств, разрешено отображать только в том случае, если не действуют ограничения взаимодействия с пользователем автомобиля (CUXR), поскольку эти интерфейсы могут требовать повышенного внимания или взаимодействия со стороны пользователя. CUXR не действуют, когда автомобиль остановлен или припаркован, но действуют всегда, когда автомобиль находится в движении.
Вам не нужно разрабатывать действия для Android Auto, поскольку Android Auto создает собственный интерфейс, оптимизированный для автомобиля, используя информацию из службы вашего медиа-браузера.
Настройте файлы манифеста вашего приложения
Прежде чем вы сможете создать службу медиабраузера, вам необходимо настроить файлы манифеста вашего приложения.
Объявите службу медиабраузера
И Android Auto, и ОС Android Automotive подключаются к вашему приложению через службу медиа-браузера для просмотра мультимедийных элементов. Объявите службу медиабраузера в манифесте, чтобы позволить Android Auto и ОС Android Automotive обнаружить эту службу и подключиться к вашему приложению.
В следующем фрагменте кода показано, как объявить службу медиабраузера в манифесте. Включите этот код в файл манифеста вашего модуля Android Automotive OS и в файл манифеста вашего приложения для телефона.
<application>
...
<service android:name=".MyMediaBrowserService"
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
...
</application>
Укажите значки приложений
Вам необходимо указать значки приложений, которые Android Auto и ОС Android Automotive могут использовать для представления вашего приложения в системном пользовательском интерфейсе. Требуются два типа значков:
- Значок запуска
- Значок атрибуции
Значок запуска
Значок средства запуска представляет ваше приложение в пользовательском интерфейсе системы, например, на средстве запуска и на панели значков. Вы можете указать, что хотите использовать значок из своего мобильного приложения для представления автомобильного мультимедийного приложения, используя следующее объявление манифеста:
<application
...
android:icon="@mipmap/ic_launcher"
...
/>
Чтобы использовать значок, отличный от значка вашего мобильного приложения, установите свойство android:icon
в элементе <service>
службы медиабраузера в манифесте:
<application>
...
<service
...
android:icon="@mipmap/auto_launcher"
...
/>
</application>
Значок атрибуции
Значок атрибуции используется в местах, где медиаконтент имеет приоритет, например, на медиакартах. Рассмотрите возможность повторного использования маленького значка, используемого для уведомлений. Эта иконка должна быть монохромной. Вы можете указать значок, который будет представлять ваше приложение, используя следующее объявление манифеста:
<application>
...
<meta-data
android:name="androidx.car.app.TintableAttributionIcon"
android:resource="@drawable/ic_status_icon" />
...
</application>
Создайте свой сервис медиабраузера
Вы создаете службу медиабраузера, расширяя класс MediaBrowserServiceCompat
. И Android Auto, и Android Automotive OS смогут использовать ваш сервис для следующих целей:
- Просмотрите иерархию контента вашего приложения, чтобы представить пользователю меню.
- Получите токен для объекта
MediaSessionCompat
вашего приложения, чтобы управлять воспроизведением звука.
Вы также можете использовать службу медиабраузера, чтобы позволить другим клиентам получать доступ к медиаконтенту из вашего приложения. Этими медиа-клиентами могут быть другие приложения на телефоне пользователя или другие удаленные клиенты.
Рабочий процесс службы медиабраузера
В этом разделе описывается, как ОС Android Automotive и Android Auto взаимодействуют со службой медиабраузера во время обычного рабочего процесса пользователя.
- Пользователь запускает ваше приложение на ОС Android Automotive или Android Auto.
- ОС Android Automotive или Android Auto связывается со службой медиабраузера вашего приложения с помощью метода
onCreate()
. В вашей реализации методаonCreate()
вы должны создать и зарегистрировать объектMediaSessionCompat
и его объект обратного вызова. - ОС Android Automotive или Android Auto вызывает метод
onGetRoot()
вашей службы, чтобы получить корневой элемент мультимедиа в иерархии контента. Корневой элемент мультимедиа не отображается; вместо этого он используется для получения большего количества контента из вашего приложения. - ОС Android Automotive или Android Auto вызывает метод
onLoadChildren()
вашей службы, чтобы получить дочерние элементы корневого элемента мультимедиа. Android Automotive OS и Android Auto отображают эти мультимедийные элементы как элементы контента верхнего уровня. См. «Структурирование корневого меню» на этой странице для получения дополнительной информации о том, что система ожидает на этом уровне. - Если пользователь выбирает просматриваемый элемент мультимедиа, метод
onLoadChildren()
вашего сервиса вызывается снова для получения дочерних элементов выбранного элемента меню. - Если пользователь выбирает воспроизводимый элемент мультимедиа, Android Automotive OS или Android Auto вызывает соответствующий метод обратного вызова сеанса мультимедиа для выполнения этого действия.
- Если это поддерживается вашим приложением, пользователь также может выполнять поиск по вашему контенту. В этом случае Android Automotive OS или Android Auto вызывают метод
onSearch()
вашей службы.
Создайте свою иерархию контента
Android Auto и Android Automotive OS вызывают службу медиабраузера вашего приложения, чтобы узнать, какой контент доступен. Для поддержки этого вам необходимо реализовать два метода в службе медиабраузера: onGetRoot()
и onLoadChildren()
Реализация onGetRoot
Метод onGetRoot()
вашего сервиса возвращает информацию о корневом узле вашей иерархии контента. Android Auto и Android Automotive OS используют этот корневой узел для запроса остального контента с помощью метода onLoadChildren()
.
В следующем фрагменте кода показана простая реализация метода onGetRoot()
:
Котлин
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)
Ява
@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); }
Более подробный пример этого метода см. в методе onGetRoot()
в примере приложения Universal Android Music Player на GitHub.
Добавить проверку пакета для onGetRoot()
Когда выполняется вызов метода onGetRoot()
вашей службы, вызывающий пакет передает идентифицирующую информацию вашей службе. Ваша служба может использовать эту информацию, чтобы решить, может ли этот пакет получить доступ к вашему контенту. Например, вы можете ограничить доступ к содержимому вашего приложения списком одобренных пакетов, сравнив clientPackageName
со своим белым списком и проверив сертификат, используемый для подписи APK пакета. Если пакет невозможно проверить, верните null
, чтобы запретить доступ к вашему контенту.
Чтобы предоставить системным приложениям, таким как Android Auto и Android Automotive OS, доступ к вашему контенту, ваша служба всегда должна возвращать ненулевой BrowserRoot
, когда эти системные приложения вызывают метод onGetRoot()
. Подпись системного приложения Android Automotive OS может различаться в зависимости от марки и модели автомобиля, поэтому вам необходимо разрешить подключения всех системных приложений для надежной поддержки Android Automotive OS.
В следующем фрагменте кода показано, как ваша служба может проверить, что вызывающий пакет является системным приложением:
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
}
Этот фрагмент кода представляет собой отрывок из класса PackageValidator
в примере приложения Universal Android Music Player на GitHub. См. этот класс для более подробного примера того, как реализовать проверку пакета для метода onGetRoot()
вашего сервиса.
Помимо разрешения системных приложений, вы должны разрешить Google Assistant подключаться к вашему MediaBrowserService
. Обратите внимание, что Google Assistant имеет отдельные названия пакетов для телефона, включая Android Auto, и для Android Automotive OS.
Реализация onLoadChildren()
После получения объекта корневого узла Android Auto и Android Automotive OS создают меню верхнего уровня, вызывая onLoadChildren()
для объекта корневого узла, чтобы получить его дочерние элементы. Клиентские приложения создают подменю, вызывая тот же метод с использованием объектов дочерних узлов.
Каждый узел в иерархии контента представлен объектом MediaBrowserCompat.MediaItem
. Каждый из этих медиа-элементов идентифицируется уникальной строкой идентификатора. Клиентские приложения рассматривают эти строки идентификаторов как непрозрачные токены. Когда клиентское приложение хочет перейти к подменю или воспроизвести элемент мультимедиа, оно передает токен. Ваше приложение отвечает за связь токена с соответствующим медиа-элементом.
В следующем фрагменте кода показана простая реализация метода onLoadChildren()
:
Котлин
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 whether 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) }
Ява
@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 whether 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); }
Полный пример этого метода см. в методе onLoadChildren()
в примере приложения Universal Android Music Player на GitHub.
Структурировать корневое меню
Android Auto и Android Automotive OS имеют определенные ограничения на структуру корневого меню. Они передаются MediaBrowserService
через корневые подсказки, которые можно прочитать через аргумент Bundle
, переданный в onGetRoot()
. Следование этим подсказкам позволяет системе оптимально отображать корневой контент в виде навигационных вкладок. Если вы не последуете этим подсказкам, некоторый корневой контент может быть удален или станет менее доступным для обнаружения системой. Отправляются две подсказки:
- Ограничение на количество корневых дочерних элементов : в большинстве случаев можно ожидать, что это число будет равно четырем. Это означает, что невозможно отобразить более четырех вкладок.
- Поддерживаемые флаги для корневых дочерних элементов : вы можете ожидать, что это значение будет
MediaItem#FLAG_BROWSABLE
. Это означает, что в виде вкладок могут отображаться только элементы, доступные для просмотра, а не воспроизводимые.
Используйте следующий код, чтобы прочитать соответствующие корневые подсказки:
Котлин
import androidx.media.utils.MediaConstants // Later, in your MediaBrowserServiceCompat. 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... }
Ява
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... }
Вы можете выбрать разветвление логики структуры иерархии контента на основе значений этих подсказок, особенно если ваша иерархия различается в зависимости от интеграции MediaBrowser
за пределами Android Auto и Android Automotive OS. Например, если вы обычно показываете корневой воспроизводимый элемент, вместо этого вы можете захотеть вложить его под корневой просматриваемый элемент из-за значения подсказки поддерживаемых флагов.
Помимо корневых подсказок, есть еще несколько рекомендаций, которые помогут обеспечить оптимальное отображение вкладок:
- Предоставьте монохромные, желательно белые, значки для каждого элемента вкладки.
- Предоставляйте короткие, но содержательные метки для каждого элемента вкладки. Короткие метки уменьшают вероятность обрезания строк.
Отображение медиа-иллюстраций
Изображение для элементов мультимедиа должно передаваться как локальный URI с помощью ContentResolver.SCHEME_CONTENT
или ContentResolver.SCHEME_ANDROID_RESOURCE
. Этот локальный URI должен разрешаться либо в растровое изображение, либо в вектор, который можно нарисовать в ресурсах приложения. Для объектов MediaDescriptionCompat
, представляющих элементы в иерархии контента, передайте URI через setIconUri()
. Для объектов MediaMetadataCompat
, представляющих воспроизводимый в данный момент элемент, передайте URI через putString()
, используя любой из следующих ключей:
-
MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI
-
MediaMetadataCompat.METADATA_KEY_ART_URI
-
MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI
Следующие шаги описывают, как загрузить произведение искусства из веб-URI и предоставить его через локальный URI. Более полный пример см. в реализации openFile()
и связанных с ней методов в примере приложения Universal Android Music Player.
Создайте
content://
соответствующий веб-URI. Служба медиабраузера и медиасеанс передают этот URI контента в Android Auto и Android Automotive OS.Котлин
fun Uri.asAlbumArtContentURI(): Uri { return Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(this.getPath()) // Make sure you trust the URI .build() }
Ява
public static Uri asAlbumArtContentURI(Uri webUri) { return new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(webUri.getPath()) // Make sure you trust the URI! .build(); }
В вашей реализации
ContentProvider.openFile()
проверьте, существует ли файл для соответствующего URI. Если нет, загрузите и кэшируйте файл изображения. В следующем фрагменте кода используется Glide .Котлин
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? { val context = this.context ?: return null val file = File(context.cacheDir, uri.path) if (!file.exists()) { val remoteUri = Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.path) .build() val cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS) cacheFile.renameTo(file) file = cacheFile } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) }
Ява
@Nullable @Override public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { Context context = this.getContext(); File file = new File(context.getCacheDir(), uri.getPath()); if (!file.exists()) { Uri remoteUri = new Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.getPath()) .build(); File cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS); cacheFile.renameTo(file); file = cacheFile; } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); }
Дополнительные сведения о поставщиках контента см. в разделе Создание поставщика контента .
Применение стилей контента
После построения иерархии контента с использованием просматриваемых или воспроизводимых элементов вы можете применить стили контента, определяющие, как эти элементы будут отображаться в автомобиле.
Вы можете использовать следующие стили контента:
- Список элементов
Этот стиль контента отдает приоритет заголовкам и метаданным над изображениями.
- Элементы сетки
Этот стиль контента отдает приоритет изображениям над заголовками и метаданными.
Установить стили контента по умолчанию
Вы можете установить глобальные настройки по умолчанию для отображения ваших медиа-элементов, включив определенные константы в пакет дополнительных возможностей BrowserRoot
метода onGetRoot()
вашей службы. Android Auto и Android Automotive OS читают этот пакет и находят эти константы, чтобы определить подходящий стиль.
В качестве ключей в комплекте можно использовать следующие аксессуары:
-
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
: указывает подсказку представления для всех доступных для просмотра элементов в дереве просмотра. -
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
: указывает подсказку представления для всех воспроизводимых элементов в дереве просмотра.
Ключи могут сопоставляться со следующими целочисленными константами, чтобы влиять на представление этих элементов:
-
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
: соответствующие элементы представлены в виде элементов списка. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
: соответствующие элементы представлены в виде элементов сетки. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM
: соответствующие элементы представлены как элементы списка «категории». Они аналогичны обычным элементам списка, за исключением того, что вокруг значков элементов применяются поля, поскольку значки выглядят лучше, когда они маленькие. Значки должны быть раскрашиваемыми векторными изображениями. Ожидается, что эта подсказка будет предоставляться только для элементов, доступных для просмотра. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM
: соответствующие элементы представлены как элементы сетки «категории». Они аналогичны обычным элементам сетки, за исключением того, что вокруг значков элементов применяются поля, поскольку значки выглядят лучше, когда они маленькие. Значки должны быть раскрашиваемыми векторными изображениями. Ожидается, что эта подсказка будет предоставляться только для элементов, доступных для просмотра.
В следующем фрагменте кода показано, как установить стиль контента по умолчанию для просматриваемых элементов в виде сеток, а для воспроизводимых элементов — в виде списков:
Котлин
import androidx.media.utils.MediaConstants @Nullable override fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) return BrowserRoot(ROOT_ID, extras) }
Ява
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); return new BrowserRoot(ROOT_ID, extras); }
Установите стили контента для каждого элемента
API стиля контента позволяет переопределить стиль контента по умолчанию для любого дочернего элемента просматриваемого медиа-элемента, а также для любого самого медиа-элемента.
Чтобы переопределить значение по умолчанию для дочерних элементов просматриваемого элемента мультимедиа, создайте дополнительный пакет в MediaDescription
элемента мультимедиа и добавьте те же самые ранее упомянутые подсказки. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
применяется к воспроизводимым дочерним элементам этого элемента, а DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
применяется к доступным для просмотра дочерним элементам этого элемента.
Чтобы переопределить значение по умолчанию для самого конкретного медиа-элемента, а не для его дочерних элементов, создайте дополнительный пакет в MediaDescription
медиа-элемента и добавьте подсказку с ключом DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM
. Используйте те же значения, которые были описаны ранее, чтобы указать представление этого элемента.
В следующем фрагменте кода показано, как создать просматриваемый объект MediaItem
, который переопределяет стиль контента по умолчанию как для себя, так и для своих дочерних элементов. Он оформляет себя как элемент списка категорий, его просматриваемые дочерние элементы — как элементы списка, а его воспроизводимые дочерние элементы — как элементы сетки:
Котлин
import androidx.media.utils.MediaConstants 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE) }
Ява
import androidx.media.utils.MediaConstants; 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE); }
Группируйте элементы, используя подсказки к заголовкам.
Чтобы сгруппировать связанные элементы мультимедиа вместе, вы используете подсказку для каждого элемента. Каждый медиа-элемент в группе должен объявить дополнительный пакет в своем MediaDescription
, который включает в себя сопоставление с ключом DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE
и идентичным строковым значением. Локализуйте эту строку, которая используется в качестве названия группы.
В следующем фрагменте кода показано, как создать MediaItem
с заголовком подгруппы "Songs"
:
Котлин
import androidx.media.utils.MediaConstants 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs") mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/) }
Ява
import androidx.media.utils.MediaConstants; 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs"); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/); }
Ваше приложение должно передавать все элементы мультимедиа, которые вы хотите сгруппировать в виде непрерывного блока. Например, предположим, что вы хотите отображать две группы медиа-элементов: «Песни» и «Альбомы» в этом порядке, и ваше приложение передает пять медиа-элементов в следующем порядке:
- Медиа-элемент A с
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Медиа-элемент B с
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
- Медиа-элемент C с
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Медиа-элемент D с
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Медиа-элемент E с
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
Поскольку элементы мультимедиа для групп «Песни» и «Альбомы» не хранятся вместе в смежных блоках, Android Auto и Android Automotive OS интерпретируют это как следующие четыре группы:
- Группа 1 под названием «Песни», содержащая медиа-элемент A.
- Группа 2 под названием «Альбомы» содержит элемент мультимедиа B.
- Группа 3 под названием «Песни» содержит мультимедийные элементы C и D.
- Группа 4 под названием «Альбомы» содержит элемент мультимедиа E.
Чтобы отобразить эти элементы в двух группах, ваше приложение должно вместо этого передавать элементы мультимедиа в следующем порядке:
- Медиа-элемент A с
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Медиа-элемент C с
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Медиа-элемент D с
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Медиа-элемент B с
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
- Медиа-элемент E с
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
Отображение дополнительных индикаторов метаданных
Вы можете включить дополнительные индикаторы метаданных, чтобы предоставлять краткую информацию о содержимом в дереве медиабраузера и во время воспроизведения. В дереве просмотра Android Auto и Android Automotive OS считывают дополнительные сведения, связанные с элементом, и ищут определенные константы, чтобы определить, какие индикаторы отображать. Во время воспроизведения мультимедиа Android Auto и Android Automotive OS считывают метаданные сеанса мультимедиа и ищут определенные константы, чтобы определить отображаемые индикаторы.
Следующие константы можно использовать как в дополнительных описаниях MediaItem
, так и в дополнительных компонентах MediaMetadata
:
-
EXTRA_DOWNLOAD_STATUS
: указывает статус загрузки элемента. Используйте эту константу как ключ; Возможными значениями являются следующие длинные константы:-
STATUS_DOWNLOADED
: элемент полностью загружен. -
STATUS_DOWNLOADING
: элемент загружается. -
STATUS_NOT_DOWNLOADED
: элемент не загружен.
-
-
METADATA_KEY_IS_EXPLICIT
: указывает, содержит ли элемент явный контент. Чтобы указать, что элемент является явным, используйте эту константу в качестве ключа и длинноеMETADATA_VALUE_ATTRIBUTE_PRESENT
в качестве значения.
Следующие константы можно использовать только в дополнительных описаниях MediaItem
:
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
: указывает на состояние завершения длинного контента, например эпизодов подкастов или аудиокниг. Используйте эту константу как ключ; Возможными значениями являются следующие целочисленные константы:-
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
: элемент вообще не воспроизводился. -
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
: элемент был воспроизведен частично, и текущая позиция находится где-то посередине. -
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
: элемент выполнен.
-
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
: указывает степень выполнения длинного контента в виде двойного значения от 0,0 до 1,0 включительно. Это дополнение предоставляет дополнительную информацию о состоянииPARTIALLY_PLAYING
, чтобы Android Auto или Android Automotive OS отображали более значимый индикатор прогресса, например индикатор выполнения. Если вы используете эту дополнительную функцию, см. раздел об обновлении индикатора выполнения в представлении просмотра во время воспроизведения контента в этом руководстве, чтобы узнать, как поддерживать этот индикатор в актуальном состоянии после его первоначального показа.
Чтобы отобразить индикаторы, которые появляются, когда пользователь просматривает дерево просмотра мультимедиа, создайте дополнительный пакет, включающий одну или несколько из этих констант, и передайте этот пакет в метод MediaDescription.Builder.setExtras()
.
В следующем фрагменте кода показано, как отображать индикаторы для явного мультимедийного элемента, завершенного на 70 %.
Котлин
import androidx.media.utils.MediaConstants val extras = Bundle() extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED) extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7) val description = MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build() return MediaBrowserCompat.MediaItem(description, /* flags */)
Ява
import androidx.media.utils.MediaConstants; Bundle extras = new Bundle(); extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED); extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build(); return new MediaBrowserCompat.MediaItem(description, /* flags */);
Чтобы отобразить индикаторы для воспроизводимого в данный момент медиа-элемента, вы можете объявить Long
значения для METADATA_KEY_IS_EXPLICIT
или EXTRA_DOWNLOAD_STATUS
в MediaMetadataCompat
вашего mediaSession
. Вы не можете отображать индикаторы DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
или DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
в режиме воспроизведения.
В следующем фрагменте кода показано, как указать, что текущая песня в представлении воспроизведения является явной и загруженной:
Котлин
import androidx.media.utils.MediaConstants mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build())
Ява
import androidx.media.utils.MediaConstants; mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build());
Обновлять индикатор выполнения в режиме просмотра по мере воспроизведения контента.
Как упоминалось ранее, вы можете использовать дополнительную опцию DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
, чтобы отобразить индикатор выполнения для частично воспроизведенного контента в режиме просмотра. Однако если пользователь продолжает воспроизводить частично воспроизведенный контент из Android Auto или Android Automotive OS, с течением времени этот индикатор становится неточным.
Чтобы Android Auto и Android Automotive OS постоянно обновляли индикатор выполнения, вы можете предоставить дополнительную информацию в MediaMetadataCompat
и PlaybackStateCompat
, чтобы связать текущий контент с элементами мультимедиа в представлении просмотра. Чтобы элемент мультимедиа имел автоматически обновляемый индикатор выполнения, должны быть выполнены следующие требования:
- При создании
MediaItem
должен отправитьDESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
в своих дополнительных функциях со значением от 0,0 до 1,0 включительно. -
MediaMetadataCompat
должен отправитьMETADATA_KEY_MEDIA_ID
со строковым значением, равным идентификатору носителя, переданному вMediaItem
. -
PlaybackStateCompat
должен включать дополнительный ключPLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID
, который сопоставляется со строковым значением, равным идентификатору мультимедиа , переданному вMediaItem
.
В следующем фрагменте кода показано, как указать, что воспроизводимый в данный момент элемент связан с элементом в представлении просмотра:
Котлин
import androidx.media.utils.MediaConstants // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. val mediaItemExtras = Bundle() mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25) val description = MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build() return MediaBrowserCompat.MediaItem(description, /* flags */) // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()) val playbackStateExtras = Bundle() playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id") mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build())
Ява
import androidx.media.utils.MediaConstants; // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. Bundle mediaItemExtras = new Bundle(); mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build(); return MediaBrowserCompat.MediaItem(description, /* flags */); // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()); Bundle playbackStateExtras = new Bundle(); playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id"); mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build());
Отображение доступных для просмотра результатов поиска
Ваше приложение может предоставлять результаты контекстного поиска, которые отображаются пользователям, когда они инициируют поисковый запрос. Android Auto и Android Automotive OS отображают эти результаты через интерфейсы поисковых запросов или через возможности, которые основаны на запросах, сделанных ранее в сеансе. Дополнительную информацию см. в разделе «Поддержка голосовых действий» данного руководства.
Чтобы отобразить доступные для просмотра результаты поиска, включите постоянный ключ BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED
в дополнительный пакет метода onGetRoot()
вашей службы, сопоставляя его с логическим значением true
.
В следующем фрагменте кода показано, как включить поддержку метода onGetRoot()
:
Котлин
import androidx.media.utils.MediaConstants @Nullable fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true) return BrowserRoot(ROOT_ID, extras) }
Ява
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true); return new BrowserRoot(ROOT_ID, extras); }
Чтобы начать предоставлять результаты поиска, переопределите метод onSearch()
в службе медиабраузера. Android Auto и ОС Android Automotive перенаправляют условия поиска пользователя в этот метод каждый раз, когда пользователь вызывает интерфейс поискового запроса или функцию «Результаты поиска».
Вы можете организовать результаты поиска из метода onSearch()
вашего сервиса, используя элементы заголовков , чтобы сделать их более удобными для просмотра. Например, если ваше приложение воспроизводит музыку, вы можете упорядочить результаты поиска по альбомам, исполнителям и песням.
В следующем фрагменте кода показана простая реализация метода onSearch()
:
Котлин
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. }
Ява
@Override public void onSearch(final String query, final Bundle extras, Result<List<MediaItem>> result) { // Detach from results to unblock the caller (if a search is expensive). result.detach(); new AsyncTask<Void, Void, Void>() { List<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); } } }.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. }
Пользовательские действия просмотра
Настраиваемые действия просмотра позволяют добавлять собственные значки и метки к объектам MediaItem
вашего приложения в мультимедийном приложении автомобиля и обрабатывать взаимодействие пользователя с этими действиями. Это позволяет вам расширить функциональность мультимедийного приложения различными способами, например добавить действия «Загрузить», «Добавить в очередь», «Воспроизвести радио», «Избранное» или «Удалить».
Если имеется больше настраиваемых действий, чем позволяет отображать OEM, пользователю будет представлено дополнительное меню.
Как они работают?
Каждое пользовательское действие просмотра определяется с помощью:
- Идентификатор действия (уникальный строковый идентификатор)
- Метка действия (текст, отображаемый пользователю)
- URI значка действия (рисуемый вектор, который можно окрашивать)
Вы определяете список пользовательских действий просмотра глобально как часть вашего BrowseRoot
. Затем вы можете прикрепить подмножество этих действий к отдельному MediaItem.
Когда пользователь взаимодействует с пользовательским действием просмотра, ваше приложение получает обратный вызов в onCustomAction()
. Затем вы можете обработать действие и при необходимости обновить список действий для MediaItem
. Это полезно для действий с отслеживанием состояния, таких как «Избранное» и «Загрузить». Для действий, которые не требуют обновления, например «Включить радио», список действий обновлять не нужно.
Вы также можете прикрепить пользовательские действия просмотра к корню узла просмотра. Эти действия будут отображаться на дополнительной панели инструментов под основной панелью инструментов.
Как реализовать пользовательские действия просмотра
Вот шаги по добавлению пользовательских действий просмотра в ваш проект:
- Переопределите два метода в вашей реализации
MediaBrowserServiceCompat
: - Анализируйте ограничения действий во время выполнения:
- В
onGetRoot()
получите максимальное количество действий, разрешенное для каждогоMediaItem
используя ключBROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT
вrootHints
Bundle
. Ограничение 0 означает, что функция не поддерживается системой.
- В
- Создайте глобальный список пользовательских действий просмотра:
- Для каждого действия создайте объект
Bundle
со следующими ключами: *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID
: идентификатор действия *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL
: метка действия *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI
: URI значка действия * Добавьте все объектыBundle
действия в список.
- Для каждого действия создайте объект
- Добавьте глобальный список в свой
BrowseRoot
:- В
Bundle
дополнительных возможностейBrowseRoot
добавьте список действий в видеParcelable
Arraylist
, используя ключBROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
.
- В
- Добавьте действия к объектам
MediaItem
:- Вы можете добавить действия к отдельным объектам
MediaItem
, включив список идентификаторов действий в дополнительные элементыMediaDescriptionCompat
с помощью ключаDESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
. Этот список должен быть подмножеством глобального списка действий, определенного вами вBrowseRoot
.
- Вы можете добавить действия к отдельным объектам
- Обработка действий и возврат прогресса или результатов:
- В
onCustomAction
обрабатывайте действие на основе идентификатора действия и любых других необходимых вам данных. Вы можете получить идентификаторMediaItem
, который инициировал действие, из дополнений, используя ключEXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID
. . - Вы можете обновить список действий для
MediaItem
, включив ключEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
в пакет прогресса или результата.
- В
Вот некоторые изменения, которые вы можете внести в свой BrowserServiceCompat
, чтобы начать работу с настраиваемыми действиями просмотра.
Переопределить BrowserServiceCompat
Вам необходимо переопределить следующие методы в MediaBrowserServiceCompat
.
public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)
public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)
Ограничение действий синтаксического анализа
Вам следует проверить, сколько поддерживается пользовательским просмотром действий.
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0) }
Создайте индивидуальный просмотр
Каждое действие должно быть упаковано в отдельный Bundle
.
- Идентификатор действия
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, "<ACTION_ID>")
- Действие этикетки
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, "<ACTION_LABEL>")
- Значок действия URI
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, "<ACTION_ICON_URI>")
Добавьте пользовательский просмотр действий в Parceable
ArrayList
Добавьте все пользовательские объекты Browse Action Bundle
в ArrayList
.
private ArrayList<Bundle> createCustomActionsList( CustomBrowseAction browseActions) { ArrayList<Bundle> browseActionsBundle = new ArrayList<>(); for (CustomBrowseAction browseAction : browseActions) { Bundle action = new Bundle(); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, browseAction.mId); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, getString(browseAction.mLabelResId)); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, browseAction.mIcon); browseActionsBundle.add(action); } return browseActionsBundle; }
Добавить собственный список действий просмотра в корень просмотра
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { Bundle browserRootExtras = new Bundle(); browserRootExtras.putParcelableArrayList( BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST, createCustomActionsList())); mRoot = new BrowserRoot(ROOT_ID, browserRootExtras); return mRoot; }
Добавить действия в MediaItem
MediaDescriptionCompat buildDescription (long id, String title, String subtitle, String description, Uri iconUri, Uri mediaUri, ArrayList<String> browseActionIds) { MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder(); bob.setMediaId(id); bob.setTitle(title); bob.setSubtitle(subtitle); bob.setDescription(description); bob.setIconUri(iconUri); bob.setMediaUri(mediaUri); Bundle extras = new Bundle(); extras.putStringArrayList( DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST, browseActionIds); bob.setExtras(extras); return bob.build(); } MediaItem mediaItem = new MediaItem(buildDescription(...), flags);
Построить результат onCustomAction
- Parse MediaId из
Bundle extras
:@Override public void onCustomAction( @NonNull String action, Bundle extras, @NonNull Result<Bundle> result){ String mediaId = extras.getString(MediaConstans.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID); }
- Для асинхронных результатов отсоедините результат.
result.detach()
- Построить пакет результатов
- Сообщение пользователю
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE, mContext.getString(stringRes))
- Обновите элемент (используйте для обновления действий в элементе)
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
- Открытый вид воспроизведения
//Shows user the PBV without changing the playback state mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
- Обновление узел
//Change current browse node to mediaId mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
- Сообщение пользователю
- Если ошибка, вызовите
result.sendError(resultBundle).
- Если обновление прогресса, вызовите
result.sendProgressUpdate(resultBundle)
. - Завершить, позвонив
result.sendResult(resultBundle)
.
Обновить состояние действия
Используя метод result.sendProgressUpdate(resultBundle)
с помощью ключа EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
, вы можете обновить MediaItem
, чтобы отразить новое состояние действия. Это позволяет вам предоставлять обратную связь с пользователем в реальном времени о прогрессе и результате их действия.
Пример: скачать действие
Вот пример того, как вы можете использовать эту функцию для реализации действия загрузки с тремя состояниями:
- Скачать: это начальное состояние действия. Когда пользователь выбирает это действие, вы можете поменять его с «загрузкой» и вызовы
sendProgressUpdate
для обновления пользовательского интерфейса. - Загрузка: это состояние указывает, что загрузка находится в процессе. Вы можете использовать это состояние, чтобы показать панель прогресса или другой индикатор для пользователя.
- Загружено: это состояние указывает, что загрузка завершена. При завершении загрузки вы можете обмениваться «загрузкой» с «загруженным» и позвонить в
sendResult
с помощью клавишиEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
, чтобы указать, что элемент должен быть обновлен. Кроме того, вы можете использовать клавишуEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE
, чтобы отобразить сообщение успеха пользователю.
Этот подход позволяет предоставить пользователю четкую обратную связь о процессе загрузки и его текущего состояния. Вы можете добавить еще больше деталей со значками, чтобы показать 25%, 50%, 75%состояния загрузки.
Пример: любимое действие
Другой пример - любимое действие с двумя состояниями:
- Любимое: это действие отображается для элементов, которые не находятся в списке фаворитов пользователя. Когда пользователь выбирает это действие, вы можете поменять его с «любимым» и позвонить
sendResult
с помощью клавишиEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
для обновления пользовательского интерфейса. - Благодаря: это действие отображается для элементов, которые находятся в списке фаворитов пользователя. Когда пользователь выбирает это действие, вы можете поменять его с «любимым» и позвонить
sendResult
с помощью клавишиEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
для обновления пользовательского интерфейса.
Этот подход обеспечивает четкий и последовательный способ для пользователей управлять своими любимыми предметами.
Эти примеры демонстрируют гибкость индивидуальных просмотра действий и то, как вы можете использовать их для реализации различных функций с обратной связью в реальном времени для улучшенного пользовательского опыта в приложении Car's Media.
Для полного примера реализации этой функции вы можете обратиться к проекту TestMediaApp
.
Включить управление воспроизведением
Android Auto и Android Automotive OS Отправляйте команды управления воспроизведением через MediaSessionCompat
вашей службы. Вы должны зарегистрировать сеанс и реализовать соответствующие методы обратного вызова.
Зарегистрировать сеанс медиа
В методе вашей сервиса Media Browser Service onCreate()
создайте MediaSessionCompat
, затем зарегистрируйте сеанс СМИ, вызовите setSessionToken()
.
Следующий фрагмент кода показывает, как создать и зарегистрировать сеанс медиа:
Котлин
override fun onCreate() { super.onCreate() ... // Start a new MediaSession. val session = MediaSessionCompat(this, "session tag").apply { // Set a callback object that implements MediaSession.Callback // to handle play control requests. setCallback(MyMediaSessionCallback()) } sessionToken = session.sessionToken ... }
Ява
public void onCreate() { super.onCreate(); ... // Start a new MediaSession. MediaSessionCompat session = new MediaSessionCompat(this, "session tag"); setSessionToken(session.getSessionToken()); // Set a callback object that implements MediaSession.Callback // to handle play control requests. session.setCallback(new MyMediaSessionCallback()); ... }
Когда вы создаете объект сеанса медиа, вы устанавливаете объект обратного вызова, который используется для обработки запросов управления воспроизведением. Вы создаете этот объект обратного вызова, предоставляя внедрение класса MediaSessionCompat.Callback
для вашего приложения. В следующем разделе обсуждается, как реализовать этот объект.
Реализовать команды PLAY
Когда пользователь запрашивает воспроизведение для предмета мультимедиа в вашем приложении, Android Automotive OS и Android Auto используют класс MediaSessionCompat.Callback
из объекта вашего приложения в MediaSessionCompat
, который они получили из службы Media Browser вашего приложения. Когда пользователь хочет контролировать воспроизведение контента, например, приостановить воспроизведение или пропуск на следующий трек, Android Auto и Android Automotive OS вызывает один из методов Callback объекта.
Чтобы справиться с воспроизведением контента, ваше приложение должно расширить Abstract MediaSessionCompat.Callback
Class и реализовать методы, которые поддерживает ваше приложение.
Реализуйте все следующие методы обратного вызова, которые имеют смысл для типа контента, который предлагает ваше приложение:
-
onPrepare()
- Призван, когда источник медиа изменяется. Android Automotive OS также вызывает этот метод сразу после загрузки. Ваше медиа -приложение должно реализовать этот метод.
-
onPlay()
- Вызывается, если пользователь выбирает воспроизведение без выбора конкретного элемента. Ваше приложение должно воспроизводить свой контент по умолчанию или, если воспроизведение было приостановлено с помощью
onPause()
, ваше приложение возобновляет воспроизведение.ПРИМЕЧАНИЕ. Ваше приложение не должно автоматически начинать воспроизведение музыки, когда Android Automotive OS или Android Auto подключаются к службу вашего браузера. Для получения дополнительной информации см. Раздел о установке начального состояния воспроизведения .
-
onPlayFromMediaId()
- Вызывается, когда пользователь решает воспроизводить конкретный элемент. Метод проходит идентификатор , который ваш сервис браузера вашего браузера, назначенный элементу мультимедиа, в вашей иерархии контента.
-
onPlayFromSearch()
- Вызывается, когда пользователь решает играть из поискового запроса. Приложение должно сделать соответствующий выбор на основе строки поиска, которая была передана.
-
onPause()
- Вызывается, когда пользователь выбирает воспроизведение.
-
onSkipToNext()
- Вызывается, когда пользователь решит пропустить следующий элемент.
-
onSkipToPrevious()
- Вызывается, когда пользователь решает пропустить предыдущий элемент.
-
onStop()
- Вызывается, когда пользователь решит остановить воспроизведение.
Переопределите эти методы в вашем приложении, чтобы обеспечить любую желаемую функциональность. Вам не нужно реализовать метод, если его функциональность не поддерживается вашим приложением. Например, если ваше приложение играет в прямом эфире, такой как спортивная трансляция, вам не нужно реализовать метод onSkipToNext()
. Вместо этого вы можете использовать реализацию по умолчанию onSkipToNext()
.
Ваше приложение не нуждается в какой -либо специальной логике, чтобы воспроизводить контент через динамики автомобиля. Когда ваше приложение получает запрос на воспроизведение контента, оно может воспроизводить звук так же, как он воспроизводит контент через телефонные динамики или наушники пользователя. Android Auto и Android Automotive OS автоматически отправляет аудиоконтент в систему автомобиля, чтобы воспроизводить динамики автомобиля.
Для получения дополнительной информации о воспроизведении звукового контента см. Обзор MediaPlayer , обзор Audio приложения и обзор Exoplayer.
Установить стандартные действия воспроизведения
Android Auto и Android Automotive OS -дисплей ОС ОСОБЕНА ОСОБЕННОСТИ ОСОБЕННОСТИ ОСОБЕННОСТЬ ИСПОЛЬЗОВАНИЯ НА ДЕЙСТВИЯХ, которые включены в объект PlaybackStateCompat
.
По умолчанию ваше приложение должно поддерживать следующие действия:
Ваше приложение может дополнительно поддержать следующие действия, если они имеют отношение к контенту приложения:
Кроме того, у вас есть возможность создать очередь воспроизведения, которая может быть отображена для пользователя, но это не требуется. Для этого вызовите методы setQueue()
и setQueueTitle()
, включите действие ACTION_SKIP_TO_QUEUE_ITEM
и определите обратный вызов onSkipToQueueItem()
.
Кроме того, добавьте поддержку для теперь играющего значка, который является индикатором того, что в настоящее время играет. Для этого вызовите метод setActiveQueueItemId()
и передайте идентификатор игрового элемента в настоящее время в очереди. Вам нужно обновить setActiveQueueItemId()
всякий раз, когда происходит изменение очереди.
Android Auto и Android Automotive OS кнопки отображения для каждого включенного действия, а также очередь воспроизведения. Когда кнопки нажимают, система вызывает соответствующий обратный вызов от MediaSessionCompat.Callback
.
Резервировать неиспользованное пространство
Android Auto и Android Automotive OS Space в пользовательском интерфейсе для действий ACTION_SKIP_TO_PREVIOUS
и ACTION_SKIP_TO_NEXT
. Если ваше приложение не поддерживает одну из этих функций, Android Auto и Android Automotive OS используйте пространство для отображения любых пользовательских действий, которые вы создаете.
Если вы не хотите заполнять эти пространства настраиваемыми действиями, вы можете зарезервировать их так, чтобы Android Auto и Android Automotive OS покидает пространство, когда ваше приложение не поддерживает соответствующую функцию. Для этого вызовите метод setExtras()
с помощью пакета дополнений, который содержит константы, которые соответствуют зарезервированным функциям. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
соответствует ACTION_SKIP_TO_NEXT
, и SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
соответствует ACTION_SKIP_TO_PREVIOUS
. Используйте эти константы в качестве клавиш в пакете и используйте логическое true
для их значений.
Установите начальный PlaybackState
Поскольку Android Auto и Android Automotive OS общаются с вашей службой Media Browser, ваша медиа -сессия передает статус воспроизведения контента, используя PlaybackStateCompat
. Ваше приложение не должно автоматически начинать воспроизведение музыки, когда Android Automotive OS или Android Auto подключаются к служению вашего браузера. Вместо этого полагайтесь на Android Auto и Android Automotive OS, чтобы возобновить или начать воспроизведение на основе состояния автомобиля или действий пользователя.
Чтобы сделать это, установите первоначальный PlaybackStateCompat
вашего сеанса медиа в STATE_STOPPED
, STATE_PAUSED
, STATE_NONE
или STATE_ERROR
.
Сессии медиа -сеансы в Android Auto и Android Automotive OS длится только на протяжении всего диска, поэтому пользователи часто начинают и останавливают эти сеансы. Чтобы продвигать беспрепятственный опыт между дисками, отслеживайте предыдущее состояние сеанса пользователя, чтобы, когда медиа -приложение получает запрос на резюме, пользователь может автоматически поднять, где они остановились, - например, последний воспроизводимый медиа -элемент, PlaybackStateCompat
и очередь.
Добавить пользовательские действия воспроизведения
Вы можете добавить пользовательские действия воспроизведения, чтобы отобразить дополнительные действия, которые поддерживает ваше приложение для медиа. Если пространство позволяет (и не зарезервировано) , Android добавляет пользовательские действия в транспортные элементы управления. В противном случае, пользовательские действия отображаются в меню переполнения. Пользовательские действия отображаются в порядке, которые они добавлены в PlaybackStateCompat
.
Используйте пользовательские действия, чтобы обеспечить поведение, отличное от стандартных действий . Не используйте их для замены или дублирования стандартных действий.
Вы можете добавить пользовательские действия, используя метод addCustomAction()
в классе PlaybackStateCompat.Builder
.
Следующий фрагмент кода показывает, как добавить пользовательское действие «Запустить радиоканал»:
Котлин
stateBuilder.addCustomAction( PlaybackStateCompat.CustomAction.Builder( CUSTOM_ACTION_START_RADIO_FROM_MEDIA, resources.getString(R.string.start_radio_from_media), startRadioFromMediaIcon ).run { setExtras(customActionExtras) build() } )
Ява
stateBuilder.addCustomAction( new PlaybackStateCompat.CustomAction.Builder( CUSTOM_ACTION_START_RADIO_FROM_MEDIA, resources.getString(R.string.start_radio_from_media), startRadioFromMediaIcon) .setExtras(customActionExtras) .build());
Для более подробного примера этого метода см. Метод setCustomAction()
в примере Universal Android Music Player на GitHub.
После создания пользовательского действия ваш сеанс медиа может ответить на действие, переопределив метод onCustomAction()
.
Следующий фрагмент кода показывает, как ваше приложение может ответить на действие «Начать радионакана»:
Котлин
override fun onCustomAction(action: String, extras: Bundle?) { when(action) { CUSTOM_ACTION_START_RADIO_FROM_MEDIA -> { ... } } }
Ява
@Override public void onCustomAction(@NonNull String action, Bundle extras) { if (CUSTOM_ACTION_START_RADIO_FROM_MEDIA.equals(action)) { ... } }
Для более подробного примера этого метода см. Метод onCustomAction
в приложении Universal Android Music Player на GitHub.
Иконки для пользовательских действий
Каждое пользовательское действие, которое вы создаете, требует ресурса значка. Приложения в автомобилях могут работать на разных размерах экрана и плотности, поэтому значки, которые вы предоставляете, должны быть векторными притяжениями . Векторный подбор позволяет масштабировать активы, не теряя детали. Вектор, подлежащий натяжению, также позволяет легко выравнивать края и углы с границами пикселей при меньших разрешениях.
Если настраиваемое действие является государственным - например, оно включает настройку воспроизведения или выключение - предоставляет различные значки для разных состояний, чтобы пользователи могли видеть изменение, когда они выберут действие.
Предоставьте альтернативные стили значков для инвалидов
Когда для текущего контекста недоступное действие недоступно, поменяйте значок «Пользовательский действие» на альтернативный значок, который показывает, что действие отключено.
Укажите аудиоформат
Чтобы указать, что в настоящее время воспроизводимый медиа использует специальный аудио -формат, вы можете указать значки, которые отображаются в автомобилях, которые поддерживают эту функцию. Вы можете установить KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI
и KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI
в комплекте Extras в данный момент воспроизводимого носителя (передача To MediaSession.setMetadata()
). Обязательно установите оба этих дополнительных данных, чтобы приспособить различные макеты.
Кроме того, вы можете установить дополнительное дополнение KEY_IMMERSIVE_AUDIO
чтобы сообщить автомобильным OEM -производителям, что это захватывающий звук, и они должны быть очень осторожны при принятии решения о том, следует ли применять звуковые эффекты, которые могут мешать захватывающему контенту.
Добавить ссылки из в настоящее время играющего в данный момент
Вы можете настроить в данный момент элемент медиа в настоящее время, чтобы его подзаголовок, описание или оба были ссылками на другие элементы медиа. Это позволяет пользователю быстро перейти к соответствующим элементам; Например, они могут перейти к другим песням того же артиста, других эпизодах этого подкаста и т. Д. Если автомобиль поддерживает эту функцию, пользователи могут нажать на ссылку на просмотр этого контента.
Чтобы добавить ссылки, настройте метаданные KEY_SUBTITLE_LINK_MEDIA_ID
(для ссылки из подзаголовок) или KEY_DESCRIPTION_LINK_MEDIA_ID
(для ссылки из описания). Для получения подробной информации см. Справочную документацию для этих полей метаданных.
Поддержать голосовые действия
Ваше медиа -приложение должно поддерживать голосовые действия, чтобы помочь водителям безопасного и удобного опыта, который сводит к минимуму отвлечения. Например, если ваше приложение воспроизводит одну медиа -элементу, пользователь может сказать « Play [Song Tile] », чтобы сказать вашему приложению воспроизвести другую песню, не просмотрев и не касаясь дисплея автомобиля. Пользователи могут инициировать запросы, нажав соответствующие кнопки На их рулевом колесе или высказывается горячие слова « ОК, Google ».
Когда Android Auto или Android Automotive OS обнаруживает и интерпретирует голосовое действие, это голосовое действие доставляется в приложение через onPlayFromSearch()
. Получив этот обратный вызов, приложение находит контент, соответствующий строке query
и начинает воспроизведение.
Пользователи могут указать различные категории терминов в своем запросе: жанр, исполнитель, альбом, название песни, радиостанция или плейлист, среди прочих. При создании поддержки для поиска учитывайте все категории, которые имеют смысл для вашего приложения. Если Android Auto или Android Automotive OS обнаруживает, что заданный запрос вписывается в определенные категории, он добавляет дополнения в параметр extras
. Можно отправить следующие дополнения:
Учетная запись для пустой строки query
, которая может быть отправлена Android Auto или Android Automotive OS, если пользователь не указывает термины поиска. Например, если пользователь говорит: « Играйте в музыку ». В этом случае ваше приложение может запустить недавно сыгранный или недавно предложенный трек.
Если поиск не может быть обработан быстро, не блокируйте в onPlayFromSearch()
. Вместо этого установите состояние воспроизведения в STATE_CONNECTING
и выполните поиск в асинхронизированном потоке.
Как только начинается воспроизведение, рассмотрите возможность заполнить очередь сеанса медиа с помощью связанного контента. Например, если пользователь просит воспроизвести альбом, ваше приложение может заполнить очередь трек -листом альбома. Также рассмотрите возможность реализации поддержки результатов поиска , чтобы пользователь мог выбрать другой трек, который соответствует их запросу.
В дополнение к « Воспроизведение » запросов, Android Auto и Android Automotive OS распознают голосовые запросы для управления воспроизведением, например, « Music Pause Music » и « Next Song », и сопоставьте эти команды с соответствующими обратными вызовами сеанса медиа, такими как onPause()
и onSkipToNext()
.
Для получения подробного примера о том, как реализовать действия воспроизведения в своем приложении, см. Google Assistant и Media Apps .
Реализовать предотвращение гарантий
Поскольку телефон пользователя подключен к динамикам их автомобиля при использовании Android Auto, вы должны принять дополнительные меры предосторожности, чтобы предотвратить отвлечение водителя.
Подавить тревоги в машине
Приложения Android Auto Media не должны начинать воспроизводить аудио через динамики автомобилей, если пользователь не начнет воспроизведение, например, нажав кнопку воспроизведения. Даже пользовательская запланированная тревога из вашего медиа-приложения не должна начинать воспроизводить музыку через динамики автомобилей.
Чтобы выполнить это требование, ваше приложение может использовать CarConnection
в качестве сигнала перед воспроизведением какого -либо звука. Ваше приложение может проверить, проецируется ли телефон на экране автомобиля, наблюдая за LiveData
для типа автомобильного соединения и проверив, равно ли он CONNECTION_TYPE_PROJECTION
.
Если телефон пользователя проецирует, медиа -приложения, которые поддерживают тревоги, должны делать одну из следующих вещей:
- Отключить сигнал тревоги.
- Воспроизведите сигнал тревоги над
STREAM_ALARM
и предоставьте пользовательский интерфейс на экране телефона, чтобы отключить сигнал тревоги.
Обрабатывать медиа -рекламу
По умолчанию Android Auto отображает уведомление, когда метаданные мультимедиа меняются во время сеанса воспроизведения звука. Когда медиа -приложение переключается от воспроизведения музыки на запуск рекламы, он отвлекает, чтобы отобразить уведомление пользователю. Чтобы Android Auto отображала уведомление в этом случае, вы должны установить метаданные метаданные средства массовой информации METADATA_KEY_IS_ADVERTISEMENT
в METADATA_VALUE_ATTRIBUTE_PRESENT
, как показано в следующем фрагменте кода:
Котлин
import androidx.media.utils.MediaConstants override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) { MediaMetadataCompat.Builder().apply { if (isAd(mediaId)) { putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) } // ...add any other properties you normally would. mediaSession.setMetadata(build()) } }
Ява
import androidx.media.utils.MediaConstants; @Override public void onPlayFromMediaId(String mediaId, Bundle extras) { MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); if (isAd(mediaId)) { builder.putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); } // ...add any other properties you normally would. mediaSession.setMetadata(builder.build()); }
Обрабатывать общие ошибки
Когда приложение испытывает ошибку, установите состояние воспроизведения в STATE_ERROR
и предоставьте сообщение об ошибке, используя метод setErrorMessage()
. См. PlaybackStateCompat
для списка кодов ошибок, которые вы можете использовать при настройке сообщения об ошибке. Сообщения об ошибках должны быть пользовательскими и локализованными с текущей локацией пользователя. Android Auto и Android Automotive OS может затем отобразить сообщение об ошибке пользователю.
Например, если содержимое недоступно в текущей области пользователя, вы можете использовать код ошибки ERROR_CODE_NOT_AVAILABLE_IN_REGION
при настройке сообщения об ошибке.
Котлин
mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build())
Ява
mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build());
Для получения дополнительной информации о состояниях ошибок см. В использовании сеанса СМИ: состояния и ошибки .
Если пользователь Android Auto должен открыть ваше телефонное приложение для разрешения ошибки, предоставьте пользователю эту информацию в вашем сообщении. Например, ваше сообщение об ошибке может сказать «Войдите в [имя вашего приложения]» вместо «Пожалуйста, войдите в систему».
Другие ресурсы
,Android Auto и Android Automotive OS помогут вам предоставить контент вашего медиа -приложения пользователям в своем автомобиле. Приложение для медиа для автомобилей должно предоставить сервис браузера для медиа -браузеров, чтобы Android Auto и Android Automotive OS или другое приложение с браузером для медиа, можно было обнаружить и отображать ваш контент.
Это руководство предполагает, что у вас уже есть медиа -приложение, которое играет аудио на телефоне и что ваше приложение для медиа соответствует архитектуре приложения Android Media .
В этом руководстве описывается необходимые компоненты MediaBrowserService
и MediaSession
, которые необходимы вашеванию для работы на Android Auto или Android Automotive OS. После того, как вы завершили основную медиа -инфраструктуру, вы можете добавить поддержку для Android Auto и добавить поддержку для Android Automotive OS в ваше приложение для медиа.
Прежде чем начать
- Просмотрите документацию Android Media API .
- Обзор Создайте медиа -приложения для руководства по проектированию.
- Просмотрите ключевые термины и понятия, перечисленные в этом разделе.
Ключевые термины и понятия
- Служба медиа -браузера
- Сервис Android, реализованный вашим медиа -приложением, которое соответствует API
MediaBrowserServiceCompat
. Ваше приложение использует эту службу для разоблачения своего контента. - СМИ браузер
- API, используемый медиа -приложениями для обнаружения сервисов браузера медиа -браузера и отображения их контента. Android Auto и Android Automotive OS Используйте медиа -браузер, чтобы найти сервис браузера вашего приложения.
- Медиа -элемент
СМИ браузер организует свой контент в дереве объектов
MediaItem
. Предмет для медиа может иметь один или оба из следующих флагов:-
FLAG_PLAYABLE
: указывает, что элемент представляет собой лист на дереве содержания. Элемент представляет собой один звуковой поток, такой как песня на альбоме, глава в аудиокниге или эпизод подкаста. -
FLAG_BROWSABLE
: Указывает, что элемент является узлом на дереве содержания, и в нем есть дети. Например, элемент представляет альбом, а его дети - песни на альбоме.
Предмет для медиа, который можно просмотреть и играть, как плейлист. Вы можете выбрать сам предмет, чтобы сыграть всех его детей, или вы можете просматривать его детей.
-
- Оптимизирован транспортным средством
Задание для приложения Android Automotive OS, которое придерживается руководящих принципов проектирования Android Automotive OS . Интерфейс для этих действий не нарисован Android Automotive OS, поэтому вы должны убедиться, что ваше приложение придерживается руководящих принципов проектирования. Как правило, это включает в себя большие цели TAP и размеры шрифтов, поддержку дневных и ночных режимов и более высокие коэффициенты контрастности.
Пользовательские интерфейсы, оптимизированные транспортными средствами, разрешаются отображаться только тогда, когда ограничения на опыт работы пользователя CAR (CUXR) не действуют, поскольку эти интерфейсы могут потребовать расширенного внимания или взаимодействия от пользователя. CuxR не действуют, когда автомобиль останавливается или припаркован, но всегда действует, когда автомобиль находится в движении.
Вам не нужно разрабатывать мероприятия для Android Auto, потому что Android Auto рисует свой собственный интерфейс, оптимизированный транспортным средством, используя информацию из вашей службы браузера.
Настройте манифестные файлы вашего приложения
Прежде чем вы сможете создать сервис браузера для медиа, вам необходимо настроить манифестные файлы вашего приложения.
Объявить свой сервис браузера в медиа
Как Android Auto, так и Android Automotive OS подключаются к вашему приложению через службу вашего браузера для Media для просмотра Media Itsems. Объявите свой сервис Media Browser в своем манифесте, чтобы Android Auto и Android Automotive OS обнаружит услугу и подключитесь к вашему приложению.
Следующий фрагмент кода показывает, как объявить ваш сервис браузера в вашем манифесте. Включите этот код в файл Manifest для вашего модуля Android Automotive OS и в файл Manifest для вашего телефона.
<application>
...
<service android:name=".MyMediaBrowserService"
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
...
</application>
Укажите значки приложений
Вам необходимо указать значки приложений, которые Android Auto и Android Automotive OS могут использовать для представления вашего приложения в пользовательском интерфейсе System. Требуются два типа значков:
- Значок запуска
- Значок атрибуции
Значок запуска
Значок запуска представляет ваше приложение в пользовательском интерфейсе System, например, в запусках и в подносе значков. Вы можете указать, что вы хотите использовать значок из своего мобильного приложения для представления вашего автомобильного медиа -приложения, используя следующее заявление о манифесте:
<application
...
android:icon="@mipmap/ic_launcher"
...
/>
Чтобы использовать другой значок, чем ваши мобильные приложения, установите элемент android:icon
на элемент Media Browser Service <service>
в манифесте:
<application>
...
<service
...
android:icon="@mipmap/auto_launcher"
...
/>
</application>
Значок атрибуции
Значок атрибуции используется в местах, где медиа -контент имеет приоритет, например, на медиа -картах. Рассмотрим повторное использование небольшой иконы, используемой для уведомлений. Эта икона должна быть монохромной. Вы можете указать значок, который используется для представления вашего приложения, используя следующее манифестное объявление:
<application>
...
<meta-data
android:name="androidx.car.app.TintableAttributionIcon"
android:resource="@drawable/ic_status_icon" />
...
</application>
Создайте сервис своего браузера медиа
Вы создаете сервис браузера медиа, расширяя класс MediaBrowserServiceCompat
. Как Android Auto, так и Android Automotive OS могут затем использовать ваш сервис для выполнения следующего:
- Просмотрите иерархию контента вашего приложения, чтобы представить меню пользователю.
- Получите токен для объекта вашего приложения
MediaSessionCompat
для управления воспроизведением звука.
Вы также можете использовать свой сервис Media Browser, чтобы позволить другим клиентам получить доступ к медиа -контенту из вашего приложения. Эти медиа -клиенты могут быть другими приложениями на телефоне пользователя, или они могут быть другими удаленными клиентами.
Рабочий процесс сервисного обслуживания медиа -браузеров
В этом разделе описывается, как Android Automotive OS и Android Auto взаимодействуют с вашим сервисом Browser во время типичного рабочего процесса пользователя.
- Пользователь запускает ваше приложение на Android Automotive OS или Android Auto.
- Android Automotive OS или Android Auto контактирует в службу Media Browser вашего приложения, используя метод
onCreate()
. В вашей реализации методаonCreate()
вы должны создать и зарегистрировать объектMediaSessionCompat
и его объект обратного вызова. - Android Automotive OS или Android Auto вызывает метод вашей службы
onGetRoot()
, чтобы получить элемент Root Media в вашей иерархии контента. Элемент root Media не отображается; Вместо этого он используется для получения большего количества контента из вашего приложения. - Android Automotive OS или Android Auto вызывает метод вашего сервиса
onLoadChildren()
чтобы получить детей root Media. Android Automotive OS и Android Auto отображают эти элементы мультимедиа в качестве верхнего уровня элементов контента. См. Структура Руновое меню на этой странице для получения дополнительной информации о том, что система ожидает на этом уровне. - Если пользователь выбирает элемент медиа -просмотра, метод вашей службы
onLoadChildren()
снова вызывается, чтобы извлечь детей из выбранного пункта меню. - Если пользователь выбирает воспроизводимый элемент медиа, Android Automotive OS или Android Auto вызывает соответствующий метод вызовов сеанса медиа для выполнения этого действия.
- При поддержке вашего приложения пользователь также может искать ваш контент. В этом случае Android Automotive OS или Android Auto Call Call Your Service's
onSearch()
.
Создайте свою иерархию контента
Android Auto и Android Automotive OS позвоните в службу Media Browser вашего приложения, чтобы узнать, какой контент доступен. Вам необходимо внедрить два метода в службе браузера для медиа -браузеров, чтобы поддержать это: onGetRoot()
и onLoadChildren()
Реализовать OngetRoot
Метод вашей службы onGetRoot()
возвращает информацию о корневом узле вашей иерархии контента. Android Auto и Android Automotive OS Используйте этот корневой узел, чтобы запросить остальную часть вашего контента, используя метод onLoadChildren()
.
В следующем фрагменте кода показана простая реализация метода onGetRoot()
:
Котлин
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)
Ява
@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); }
Для более подробного примера этого метода см. Метод onGetRoot()
в примере Universal Android Music Player на GitHub.
Добавьте проверку пакета для OnGetRoot ()
Когда звонок будет выполнен в методе вашего сервиса onGetRoot()
, вызовный пакет передает идентификацию информации в ваш сервис. Ваша служба может использовать эту информацию, чтобы решить, может ли этот пакет получить доступ к вашему контенту. Например, вы можете ограничить доступ к контенту вашего приложения в списке утвержденных пакетов, сравнив clientPackageName
с вашим списком Allist и проверив сертификат, используемый для подписи APK пакета. Если пакет не может быть проверен, верните null
, чтобы отрицать доступ к вашему контенту.
Для предоставления системных приложений, таких как Android Auto и Android Automotive OS, с доступом к вашему контенту, ваш сервис всегда должен возвращать не нулевый BrowserRoot
, когда эти системы приложения вызывают метод onGetRoot()
. Подпись приложения Android Automotive OS System может варьироваться в зависимости от марки и модели автомобиля, поэтому вам необходимо разрешить подключения от всех системных приложений для надежной поддержки Android Automotive OS.
Следующий фрагмент кода показывает, как ваша служба может подтвердить, что вызовный пакет является системным приложением:
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
}
Этот фрагмент кода является выдержкой из класса PackageValidator
в примере Universal Android Music Player на GitHub. Смотрите этот класс для более подробного примера того, как реализовать проверку пакета для метода вашей службы onGetRoot()
.
В дополнение к разрешению системных приложений, вы должны позволить Google Assistant подключиться к вашему MediaBrowserService
. Обратите внимание, что Google Assistant имеет отдельные имена пакетов для телефона, который включает Android Auto и для Android Automotive OS.
Реализовать OnLoadChildren ()
После получения объекта корневого узла Android Auto и Android Automotive OS создайте меню верхнего уровня, позвонив onLoadChildren()
на объекте корневого узла, чтобы получить своих детей. Клиентские приложения создают подменген, вызывая этот же метод, используя объекты детского узла.
Каждый узел в вашей иерархии контента представлен объектом MediaBrowserCompat.MediaItem
. Каждый из этих элементов мультимедиа идентифицируется уникальной идентификационной строкой. Клиентские приложения рассматривают эти строки идентификатора как непрозрачные токены. Когда клиентское приложение хочет просмотреть подменю или сыграть в медиа -элементе, оно передает токен. Ваше приложение отвечает за связь токена с соответствующим элементом медиа.
В следующем фрагменте кода показана простая реализация метода onLoadChildren()
:
Котлин
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 whether 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) }
Ява
@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 whether 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); }
Для полного примера этого метода см. Метод onLoadChildren()
в примере Universal Android Music Player на GitHub.
Структура корневого меню
Android Auto и Android Automotive OS имеют особые ограничения в отношении структуры корневого меню. Они передаются в MediaBrowserService
через корневые подсказки, которые можно прочитать через аргумент Bundle
, переданный в onGetRoot()
. Следуя этим подсказкам позволяет системе оптимально отображать корневое содержание в качестве навигационных вкладок. Если вы не следуете этим подсказкам, некоторые корневые содержания могут быть отброшены или сделаны менее обнаруженными в системе. Отправлены два подсказки:
- Ограничение на количество корневых детей : для большинства случаев вы можете ожидать, что это число будет четыре. Это означает, что более четырех вкладок не могут быть показаны.
- Поддерживаемые флаги на корневых детях : вы можете ожидать, что это значение будет
MediaItem#FLAG_BROWSABLE
. Это означает, что только просмотренные элементы - не воспроизводимые элементы - могут быть показаны в качестве вкладок.
Используйте следующий код, чтобы прочитать соответствующие корневые подсказки:
Котлин
import androidx.media.utils.MediaConstants // Later, in your MediaBrowserServiceCompat. 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... }
Ява
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 Android Automotive OS. 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 the root hints, there are a couple additional guidelines to follow to help ensure that tabs render optimally:
- Supply monochrome, preferably white, icons for each tab item.
- Supply short but meaningful labels for each tab item. Keeping labels short reduces the chance of the strings being truncated.
Display media artwork
Artwork for media items must be passed as a local URI using either ContentResolver.SCHEME_CONTENT
or ContentResolver.SCHEME_ANDROID_RESOURCE
. This local URI must resolve to either a bitmap or a vector drawable in the application's resources. For MediaDescriptionCompat
objects representing items in the content hierarchy, pass the URI through setIconUri()
. For MediaMetadataCompat
objects representing the currently playing item, pass the URI through putString()
, using any of the following keys:
-
MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI
-
MediaMetadataCompat.METADATA_KEY_ART_URI
-
MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI
The following steps describe how to download art from a web URI and expose it through a local URI. For a more complete example, see the implementation of openFile()
and the surrounding methods in the Universal Android Music Player sample app.
Build a
content://
URI corresponding to the web URI. The media browser service and media session pass this content URI to Android Auto and Android Automotive OS.Котлин
fun Uri.asAlbumArtContentURI(): Uri { return Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(this.getPath()) // Make sure you trust the URI .build() }
Ява
public static Uri asAlbumArtContentURI(Uri webUri) { return new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(webUri.getPath()) // Make sure you trust the URI! .build(); }
In your implementation of
ContentProvider.openFile()
, check whether a file exists for the corresponding URI. If not, download and cache the image file. The following code snippet uses Glide .Котлин
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? { val context = this.context ?: return null val file = File(context.cacheDir, uri.path) if (!file.exists()) { val remoteUri = Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.path) .build() val cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS) cacheFile.renameTo(file) file = cacheFile } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) }
Ява
@Nullable @Override public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { Context context = this.getContext(); File file = new File(context.getCacheDir(), uri.getPath()); if (!file.exists()) { Uri remoteUri = new Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.getPath()) .build(); File cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS); cacheFile.renameTo(file); file = cacheFile; } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); }
For more details about content providers, refer to Creating a content provider .
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:
- Список элементов
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 this bundle and look for those constants to determine the appropriate style.
The following extras can be used as keys in the bundle:
-
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
: indicates a presentation hint for all browsable items within the browse tree. -
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
: indicates a presentation hint for all playable items within the browse tree.
The keys can map to the following integer constant values to influence the presentation of those items:
-
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
: the corresponding items are presented as list items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
: the corresponding items are presented as grid items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM
: the corresponding items are presented as "category" list items. These are the same as ordinary list items except that margins are applied around the items' icons, since the icons look better when they are small. The icons must be tintable vector drawables. This hint is expected to only be provided for browsable items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM
: the corresponding items are presented as "category" grid items. These are the same as ordinary grid items, except that margins are applied around the items' icons, since the icons look better when they are small. The icons must be tintable vector drawables. This hint is expected to only be provided for browsable items.
The following code snippet shows how to set the default content style for browsable items to grids and playable items to lists:
Котлин
import androidx.media.utils.MediaConstants @Nullable override fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) return BrowserRoot(ROOT_ID, extras) }
Ява
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); 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, as well as any media item itself.
To override the default for a browsable media item's children , create an extras bundle in the MediaDescription
of the media item and add the same previously mentioned hints. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
applies to that item's playable children, while DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
applies to that item's browsable children.
To override the default for a particular media item itself , not its children, create an extras bundle in the MediaDescription
of the media item and add a hint with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM
. Use the same values described previously to specify that item's presentation.
The following code snippet shows how to create a browsable MediaItem
that overrides the default content style for both itself and its children. It styles itself as a category list item, its browsable children as list items, and its playable children as grid items:
Котлин
import androidx.media.utils.MediaConstants 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE) }
Ява
import androidx.media.utils.MediaConstants; 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); 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 includes a mapping with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE
and an identical string value. Localize this string, which is used as the title of the group.
The following code snippet shows how to create a MediaItem
with a subgroup heading of "Songs"
:
Котлин
import androidx.media.utils.MediaConstants 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs") mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/) }
Ява
import androidx.media.utils.MediaConstants; 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs"); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/); }
Your app must pass all the media items that you want to group together as a contiguous block. For example, suppose that you want to display two groups of media items, "Songs" and "Albums," in that order, and your app passes in five media items in the following order:
- Media item A with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item B with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
- Media item C with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item D with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item E with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "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 interprets this as the following four groups:
- 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 must pass the media items in the following order instead:
- Media item A with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item C with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item D with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item B with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
- Media item E with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
Display additional metadata indicators
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 look 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.
The following constants can be used in both MediaItem
description extras and MediaMetadata
extras:
-
EXTRA_DOWNLOAD_STATUS
: indicates the download status of an item. Use this constant as the key; the following long constants are the possible values:-
STATUS_DOWNLOADED
: the item is completely downloaded. -
STATUS_DOWNLOADING
: the item is being downloaded. -
STATUS_NOT_DOWNLOADED
: the item is not downloaded.
-
-
METADATA_KEY_IS_EXPLICIT
: indicates whether the item contains explicit content. To indicate an item is explicit, use this constant as the key and the longMETADATA_VALUE_ATTRIBUTE_PRESENT
as the value.
The following constants can only be used in MediaItem
description extras:
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
: indicates the completion state of long-form content, such as podcast episodes or audiobooks. Use this constant as the key; the following integer constants are the possible values:-
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
: the item has not been played at all. -
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
: the item has been partially played, and the current position is somewhere in the middle. -
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
: the item has been completed.
-
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
: indicates the amount of completion progress on long-form content as a double between 0.0 and 1.0, inclusive. This extra provides more information about thePARTIALLY_PLAYING
state so that Android Auto or Android Automotive OS displays a more meaningful progress indicator, such as a progress bar. If you use this extra, see the section about updating the progress bar in the browse view as content is playing in this guide to learn how to keep this indicator up to date after its initial impression.
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 media item that is 70% complete:
Котлин
import androidx.media.utils.MediaConstants val extras = Bundle() extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED) extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7) val description = MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build() return MediaBrowserCompat.MediaItem(description, /* flags */)
Ява
import androidx.media.utils.MediaConstants; Bundle extras = new Bundle(); extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED); extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7); 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 METADATA_KEY_IS_EXPLICIT
or EXTRA_DOWNLOAD_STATUS
in the MediaMetadataCompat
of your mediaSession
. You can't display the DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
or DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
indicators on the playback view.
The following code snippet shows how to indicate that the current song in the playback view is explicit and downloaded:
Котлин
import androidx.media.utils.MediaConstants mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build())
Ява
import androidx.media.utils.MediaConstants; mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build());
Update the progress bar in the browse view as content is playing
As previously mentioned, you can use the DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
extra to show a progress bar for partially played content in the browse view. However, if a user continues playing the partially played content from Android Auto or Android Automotive OS, that indicator becomes inaccurate as time passes.
For Android Auto and Android Automotive OS to keep the progress bar up to date, you can supply additional information in MediaMetadataCompat
and PlaybackStateCompat
to link ongoing content to media items in the browse view. The following requirements must be met for the media item to have an automatically updating progress bar:
- When it is created, the
MediaItem
must sendDESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
in its extras with a value between 0.0 and 1.0, inclusive. - The
MediaMetadataCompat
must sendMETADATA_KEY_MEDIA_ID
with a string value equal to the media ID passed to theMediaItem
. - The
PlaybackStateCompat
must include an extra with the keyPLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID
that maps to a string value equal to the media ID passed to theMediaItem
.
The following code snippet shows how to indicate that the currently playing item is linked to an item in the browse view:
Котлин
import androidx.media.utils.MediaConstants // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. val mediaItemExtras = Bundle() mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25) val description = MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build() return MediaBrowserCompat.MediaItem(description, /* flags */) // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()) val playbackStateExtras = Bundle() playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id") mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build())
Ява
import androidx.media.utils.MediaConstants; // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. Bundle mediaItemExtras = new Bundle(); mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build(); return MediaBrowserCompat.MediaItem(description, /* flags */); // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()); Bundle playbackStateExtras = new Bundle(); playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id"); mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build());
Display browsable search results
Your app can provide contextual search results that display to users when they initiate a search query. Android Auto and Android Automotive OS show these results through search query interfaces or through affordances that pivot on queries made earlier in the session. To learn more, see the Support voice actions section in this guide.
To display browsable search results, include the constant key BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED
in the extras bundle of your service's onGetRoot()
method, mapping to the boolean true
.
The following code snippet shows how to enable support in the onGetRoot()
method:
Котлин
import androidx.media.utils.MediaConstants @Nullable fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true) return BrowserRoot(ROOT_ID, extras) }
Ява
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_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 the user's search terms to this method whenever a user invokes a search query interface or “Search 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:
Котлин
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. }
Ява
@Override public void onSearch(final String query, final Bundle extras, Result<List<MediaItem>> result) { // Detach from results to unblock the caller (if a search is expensive). result.detach(); new AsyncTask<Void, Void, Void>() { List<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); } } }.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. }
Custom Browse Actions
Custom Browse Actions allow you to add custom icons and labels to your app's MediaItem
objects in the car's media app, and handle user interactions with these actions. This lets you extend the functionality of the Media App in a variety of ways, like adding "Download", "Add to Queue", "Play Radio", "Favorite", or "Remove" actions.
If there are more custom actions than the OEM allows to be displayed, an overflow menu will be presented to the user.
Как они работают?
Each Custom Browse Action is defined with:
- An Action ID (a unique string identifier)
- An Action Label (the text displayed to the user)
- An Action Icon URI (a vector drawable that can be tinted)
You define a list of Custom Browse Actions globally as part of your BrowseRoot
. Then you can attach a subset of these actions to individual MediaItem.
When a user interacts with a Custom Browse Action, your app receives a callback in onCustomAction()
. You can then handle the action and update the list of actions for the MediaItem
if necessary. This is useful for stateful actions like "Favorite" and "Download". For actions that don't need updating, like "Play Radio", you don't need to update the list of actions.
You can also attach Custom Browse Actions to a browse node root. These actions will be displayed in a secondary toolbar under the main toolbar.
How to implement Custom Browse Actions
Here are the steps to add Custom Browse Actions to your project:
- Override two methods in your
MediaBrowserServiceCompat
implementation: - Parse the action limits at runtime:
- In
onGetRoot()
, get the maximum number of actions allowed for eachMediaItem
using the keyBROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT
in therootHints
Bundle
. A limit of 0 indicates that the feature is not supported by the system.
- In
- Build the global list of Custom Browse Actions:
- For each action, create a
Bundle
object with the following keys: *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID
: The action ID *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL
: The action label *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI
: The action icon URI * Add all the actionBundle
objects to a list.
- For each action, create a
- Add the global list to your
BrowseRoot
:- In the
BrowseRoot
extrasBundle
, add the list of actions as aParcelable
Arraylist
using the keyBROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
.
- In the
- Add actions to your
MediaItem
objects:- You can add actions to individual
MediaItem
objects by including the list of action IDs in theMediaDescriptionCompat
extras using the keyDESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
. This list must be a subset of the global list of actions you defined in theBrowseRoot
.
- You can add actions to individual
- Handle actions and return progress or results:
- In
onCustomAction
, handle the action based on the action ID and any other data you need. You can get the ID of theMediaItem
that triggered the action from the extras using the keyEXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID
. . - You can update the list of actions for a
MediaItem
by including the keyEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
in the progress or result bundle.
- In
Here are some changes you can make in your BrowserServiceCompat
to get started with Custom Browse Actions.
Override BrowserServiceCompat
You need to override the following methods in MediaBrowserServiceCompat
.
public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)
public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)
Parse actions limit
You should check to see how many Custom Browse Actions are supported.
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0) }
Build a Custom Browse Action
Each action needs to be packed into a separate Bundle
.
- Action ID
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, "<ACTION_ID>")
- Action Label
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, "<ACTION_LABEL>")
- Action Icon URI
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, "<ACTION_ICON_URI>")
Add Custom Browse Actions to Parceable
ArrayList
Add all Custom Browse Action Bundle
objects into an ArrayList
.
private ArrayList<Bundle> createCustomActionsList( CustomBrowseAction browseActions) { ArrayList<Bundle> browseActionsBundle = new ArrayList<>(); for (CustomBrowseAction browseAction : browseActions) { Bundle action = new Bundle(); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, browseAction.mId); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, getString(browseAction.mLabelResId)); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, browseAction.mIcon); browseActionsBundle.add(action); } return browseActionsBundle; }
Add Custom Browse Action list to the browse root
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { Bundle browserRootExtras = new Bundle(); browserRootExtras.putParcelableArrayList( BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST, createCustomActionsList())); mRoot = new BrowserRoot(ROOT_ID, browserRootExtras); return mRoot; }
Add actions to a MediaItem
MediaDescriptionCompat buildDescription (long id, String title, String subtitle, String description, Uri iconUri, Uri mediaUri, ArrayList<String> browseActionIds) { MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder(); bob.setMediaId(id); bob.setTitle(title); bob.setSubtitle(subtitle); bob.setDescription(description); bob.setIconUri(iconUri); bob.setMediaUri(mediaUri); Bundle extras = new Bundle(); extras.putStringArrayList( DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST, browseActionIds); bob.setExtras(extras); return bob.build(); } MediaItem mediaItem = new MediaItem(buildDescription(...), flags);
Build onCustomAction
result
- Parse mediaId from
Bundle extras
:@Override public void onCustomAction( @NonNull String action, Bundle extras, @NonNull Result<Bundle> result){ String mediaId = extras.getString(MediaConstans.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID); }
- For asynchronous results detach result.
result.detach()
- Build result bundle
- Сообщение пользователю
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE, mContext.getString(stringRes))
- Update item(use to update actions in an item)
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
- Open Playback view
//Shows user the PBV without changing the playback state mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
- Update Browse Node
//Change current browse node to mediaId mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
- Сообщение пользователю
- If an error, call
result.sendError(resultBundle).
- If progress update, call
result.sendProgressUpdate(resultBundle)
. - Finish by calling
result.sendResult(resultBundle)
.
Update Action State
By using the result.sendProgressUpdate(resultBundle)
method with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key, you can update the MediaItem
to reflect the new state of the action. This lets you provide real-time feedback to the user about the progress and result of their action.
Example: Download Action
Here's an example of how you can use this feature to implement a download action with three states:
- Download: This is the initial state of the action. When the user selects this action, you can swap it with "Downloading" and call
sendProgressUpdate
to update the UI. - Downloading: This state indicates that the download is in progress. You can use this state to show a progress bar or another indicator to the user.
- Downloaded: This state indicates that the download is complete. When the download finishes, you can swap "Downloading" with "Downloaded" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to indicate that the item should be refreshed. Additionally, you can use theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE
key to display a success message to the user.
This approach lets you provide clear feedback to the user about the download process and its current state. You can add even more detail with icons to show 25%, 50%, 75% download states.
Example: Favorite Action
Another example is a favorite action with two states:
- Favorite: This action is displayed for items that are not in the user's favorites list. When the user selects this action, you can swap it with "Favorited" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to update the UI. - Favorited: This action is displayed for items that are in the user's favorites list. When the user selects this action, you can swap it with "Favorite" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to update the UI.
This approach provides a clear and consistent way for users to manage their favorite items.
These examples showcase the flexibility of Custom Browse Actions and how you can use them to implement a variety of functionalities with real-time feedback for an enhanced user experience in the car's media app.
For a complete example implementation of this feature, you can refer to the TestMediaApp
project.
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:
Котлин
override fun onCreate() { super.onCreate() ... // Start a new MediaSession. val session = MediaSessionCompat(this, "session tag").apply { // Set a callback object that implements MediaSession.Callback // to handle play control requests. setCallback(MyMediaSessionCallback()) } sessionToken = session.sessionToken ... }
Ява
public void onCreate() { super.onCreate(); ... // Start a new MediaSession. MediaSessionCompat session = new MediaSessionCompat(this, "session tag"); setSessionToken(session.getSessionToken()); // Set a callback object that implements MediaSession.Callback // to handle play control requests. 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.
Implement all 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 must play its default content or, if playback was paused with
onPause()
, your app resumes 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 the section about setting the 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 must 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.
Override these methods in your app to provide any desired functionality. You don't need to implement a method if its functionality isn't supported by your app. For example, if your app plays a live stream, such as a sports broadcast, you don't need to implement the onSkipToNext()
method. You can use the default implementation of onSkipToNext()
instead.
Your app doesn't need any special logic to play content through the car's speakers. When your app receives a request to play content, it can play audio the same way that it plays 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 MediaPlayer overview , Audio app overview , and the ExoPlayer overview .
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:
Your app can additionally support the following actions if they are relevant to the app's content:
In addition, you have the option to create a play queue that can be displayed for the user, but it is not required. To do this, call the setQueue()
and setQueueTitle()
methods, enable the ACTION_SKIP_TO_QUEUE_ITEM
action, and define the callback onSkipToQueueItem()
.
Also, add support for the Now playing icon, which is an indicator for what is currently playing. To do this, call the setActiveQueueItemId()
method and pass the ID of the currently playing item in the queue. You need to update setActiveQueueItemId()
whenever there is a queue change.
Android Auto and Android Automotive OS display buttons for each enabled action as well as the playback queue. When the buttons are clicked, the system invokes their corresponding callback from MediaSessionCompat.Callback
.
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. 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 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 the reserved functions. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
corresponds to ACTION_SKIP_TO_NEXT
, and SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
corresponds to ACTION_SKIP_TO_PREVIOUS
. Use these constants as keys in the bundle, and use the boolean true
for their values.
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 PlaybackStateCompat
. 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 PlaybackStateCompat
of your media session to STATE_STOPPED
, STATE_PAUSED
, STATE_NONE
, or STATE_ERROR
.
Media sessions within Android Auto and Android Automotive OS only last for the duration of the drive, so users start and stop these sessions frequently. To promote a seamless experience between drives, keep track of the user's previous session state, so that when the media app receives a resume request, the user can automatically pick up where they left off—for example, the last played media item, the PlaybackStateCompat
, and the queue.
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 display in the overflow menu. Custom actions display in the order they are added to the PlaybackStateCompat
.
Use custom actions to provide behavior distinct from standard actions . Don't use them to replace or duplicate standard actions.
You can add custom 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:
Котлин
stateBuilder.addCustomAction( PlaybackStateCompat.CustomAction.Builder( CUSTOM_ACTION_START_RADIO_FROM_MEDIA, resources.getString(R.string.start_radio_from_media), startRadioFromMediaIcon ).run { setExtras(customActionExtras) build() } )
Ява
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 might respond to a “Start a radio channel” action:
Котлин
override fun onCustomAction(action: String, extras: Bundle?) { when(action) { CUSTOM_ACTION_START_RADIO_FROM_MEDIA -> { ... } } }
Ява
@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.
If a custom action is stateful—for example, it toggles a playback setting on or off—provide different icons for the different states, so users can see a change when they select the action.
Provide alternative icon styles for disabled actions
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.
Indicate audio format
To indicate that currently playing media uses a special audio format, you can specify icons that are rendered in cars that support this feature. You can set the KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI
and the KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI
in the extras bundle of the currently playing media item (passed to MediaSession.setMetadata()
). Make sure to set both of those extras, to accommodate different layouts.
In addition, you can set the KEY_IMMERSIVE_AUDIO
extra to tell car OEMs that this is immersive audio, and they should be very careful when deciding whether to apply audio effects that might interfere with the immersive content.
Add links from currently-playing item
You can configure the currently-playing media item so its subtitle, description, or both are links to other media items. That lets the user jump quickly to related items; for example, they might jump to other songs by the same artist, other episodes of that podcast, etc. If the car supports this feature, users can tap the link to browse to that content.
To add links, configure the KEY_SUBTITLE_LINK_MEDIA_ID
metadata (to link from the subtitle) or KEY_DESCRIPTION_LINK_MEDIA_ID
(to link from the description). For details, see the reference documentation for those metadata fields.
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 playing one media item, the user can say “ Play [song title] " to tell your app to play a different song without looking at or touching the car's display. Users can initiate queries by clicking the appropriate buttons on their steering wheel or speaking the hotwords " OK Google ."
When Android Auto or Android Automotive OS detects and interprets a voice action, that voice action is delivered to the app through onPlayFromSearch()
. On receiving this callback, the app finds content matching the query
string and starts playback.
Users can specify different categories of terms in their query: genre, artist, album, song name, radio station, or playlist, among others. When building support for search, account for all the categories that make sense for your app. If Android Auto or Android Automotive OS detects that a given query fits into certain categories, it appends extras in the extras
parameter. The following extras can be sent:
Account for an empty query
string, which can be sent by Android Auto or Android Automotive OS if the user doesn't specify search terms. For example, if the user says " Play some music ." In that case, your app might choose to start a recently played or newly suggested track.
If a search cannot be processed quickly, do not block in onPlayFromSearch()
. Instead, set the playback state to STATE_CONNECTING
and perform the search on an async thread.
Once playback begins, consider populating the media session's queue with related content. For example, if the user requests an album to be played, your app might fill the queue with the album's tracklist. Also consider implementing support for browsable search results so a user can choose a different track that matches their query.
In addition to " play " queries, Android Auto and Android Automotive OS recognize voice queries to control playback like " pause music " and " next song " and match these commands to the appropriate media session callbacks, like onPause()
and onSkipToNext()
.
For a detailed example on how to implement voice-enabled playback actions in your app, see 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.
Suppress alarms in the car
Android Auto media apps must not start playing audio through the car speakers unless the user starts playback by, for example, pressing a play button. Even a user-scheduled alarm from your media app must not start playing music through the car speakers.
To fulfill this requirement, your app can use CarConnection
as a signal before playing any audio. Your app can check whether the phone is projecting to a car screen by observing the LiveData
for the car connection type and checking whether it is equal to CONNECTION_TYPE_PROJECTION
.
If the user's phone is projecting, 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.
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 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 METADATA_KEY_IS_ADVERTISEMENT
to METADATA_VALUE_ATTRIBUTE_PRESENT
, as shown in the following code snippet:
Котлин
import androidx.media.utils.MediaConstants override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) { MediaMetadataCompat.Builder().apply { if (isAd(mediaId)) { putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) } // ...add any other properties you normally would. mediaSession.setMetadata(build()) } }
Ява
import androidx.media.utils.MediaConstants; @Override public void onPlayFromMediaId(String mediaId, Bundle extras) { MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); if (isAd(mediaId)) { builder.putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); } // ...add any other properties you normally would. mediaSession.setMetadata(builder.build()); }
Handle general errors
When the app experiences an error, set the playback state to STATE_ERROR
and provide an error message using the setErrorMessage()
method. See PlaybackStateCompat
for a list of error codes that you can use when setting the error message. 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 example, if content is not available in the user's current region, you can use the ERROR_CODE_NOT_AVAILABLE_IN_REGION
error code when setting the error message.
Котлин
mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build())
Ява
mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build());
For more information about error states, see Using a media session: States and errors .
If an Android Auto user needs to open your phone app to resolve an error, provide that information to the user in your message. For example, your error message might say "Sign in to [your app name]" instead of "Please sign in."
Другие ресурсы
,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.
Прежде чем начать
- Review the Android media API documentation .
- Review Create media apps for design guidance.
- Review the key terms and concepts listed in this section.
Ключевые термины и понятия
- 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 the following flags:-
FLAG_PLAYABLE
: 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. -
FLAG_BROWSABLE
: indicates that 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 to browse media items. Declare your media browser service in your manifest to let Android Auto and Android Automotive OS discover the service and connect to your app.
The following code snippet shows how to declare your media browser service in your manifest. 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 app icons
You need to specify app icons that Android Auto and Android Automotive OS can use to represent your app in the system UI. Two icon types are required:
- Значок запуска
- Attribution icon
Значок запуска
The launcher icon represents your app in the system UI, such as on the launcher and in the tray of icons. You can specify that you want to use the icon from your mobile app to represent your car media app using the following manifest declaration:
<application
...
android:icon="@mipmap/ic_launcher"
...
/>
To use a different icon than your mobile app's, set the android:icon
property on your media browser service's <service>
element in the manifest:
<application>
...
<service
...
android:icon="@mipmap/auto_launcher"
...
/>
</application>
Attribution icon
The attribution icon is used in places where media content takes precedence, such as on media cards. Consider reusing the small icon used for notifications. This icon must be monochrome. You can specify an icon that is used to represent your app using the following manifest declaration:
<application>
...
<meta-data
android:name="androidx.car.app.TintableAttributionIcon"
android:resource="@drawable/ic_status_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 to present a menu to the user.
- Get the token for your app's
MediaSessionCompat
object to control audio playback.
You can also use your media browser service to let other clients access media content from your app. These media clients might be other apps on a user's phone, or they can be other remote clients.
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.
- The 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. See Structure the root menu on this page for more information on what the system expects at this level. - 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:
Котлин
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)
Ява
@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 whether 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 allowlist 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.
To provide system apps, such as Android Auto and Android Automotive OS, with access to your content, your service must always return a non-null BrowserRoot
when these system apps call the onGetRoot()
method. The signature of the Android Automotive OS system app can vary depending on the make and model of the car, so you need to permit connections from all system apps to support Android Automotive OS robustly.
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.
In addition to allowing system apps, you must let the Google Assistant connect to your MediaBrowserService
. Note that the Google Assistant has separate package names for the phone, which includes Android Auto, and for Android Automotive OS.
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.
The following code snippet shows a simple implementation of onLoadChildren()
method:
Котлин
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 whether 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) }
Ява
@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 whether 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.
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()
. Following these hints lets the system display the root content optimally as navigational tabs. If you don't follow these hints, some root content might be dropped or made less discoverable by the system. Two hints are sent:
- A limit on the number of root children : for the majority of cases, you can expect this number to be four. This means that over four tabs cannot be shown.
- Supported flags on the root children : you can expect this value to be
MediaItem#FLAG_BROWSABLE
. This means that only browsable items—not playable items—can be shown as tabs.
Use the following code to read the relevant root hints:
Котлин
import androidx.media.utils.MediaConstants // Later, in your MediaBrowserServiceCompat. 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... }
Ява
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 Android Automotive OS. 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 the root hints, there are a couple additional guidelines to follow to help ensure that tabs render optimally:
- Supply monochrome, preferably white, icons for each tab item.
- Supply short but meaningful labels for each tab item. Keeping labels short reduces the chance of the strings being truncated.
Display media artwork
Artwork for media items must be passed as a local URI using either ContentResolver.SCHEME_CONTENT
or ContentResolver.SCHEME_ANDROID_RESOURCE
. This local URI must resolve to either a bitmap or a vector drawable in the application's resources. For MediaDescriptionCompat
objects representing items in the content hierarchy, pass the URI through setIconUri()
. For MediaMetadataCompat
objects representing the currently playing item, pass the URI through putString()
, using any of the following keys:
-
MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI
-
MediaMetadataCompat.METADATA_KEY_ART_URI
-
MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI
The following steps describe how to download art from a web URI and expose it through a local URI. For a more complete example, see the implementation of openFile()
and the surrounding methods in the Universal Android Music Player sample app.
Build a
content://
URI corresponding to the web URI. The media browser service and media session pass this content URI to Android Auto and Android Automotive OS.Котлин
fun Uri.asAlbumArtContentURI(): Uri { return Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(this.getPath()) // Make sure you trust the URI .build() }
Ява
public static Uri asAlbumArtContentURI(Uri webUri) { return new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(webUri.getPath()) // Make sure you trust the URI! .build(); }
In your implementation of
ContentProvider.openFile()
, check whether a file exists for the corresponding URI. If not, download and cache the image file. The following code snippet uses Glide .Котлин
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? { val context = this.context ?: return null val file = File(context.cacheDir, uri.path) if (!file.exists()) { val remoteUri = Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.path) .build() val cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS) cacheFile.renameTo(file) file = cacheFile } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) }
Ява
@Nullable @Override public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { Context context = this.getContext(); File file = new File(context.getCacheDir(), uri.getPath()); if (!file.exists()) { Uri remoteUri = new Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.getPath()) .build(); File cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS); cacheFile.renameTo(file); file = cacheFile; } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); }
For more details about content providers, refer to Creating a content provider .
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:
- Список элементов
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 this bundle and look for those constants to determine the appropriate style.
The following extras can be used as keys in the bundle:
-
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
: indicates a presentation hint for all browsable items within the browse tree. -
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
: indicates a presentation hint for all playable items within the browse tree.
The keys can map to the following integer constant values to influence the presentation of those items:
-
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
: the corresponding items are presented as list items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
: the corresponding items are presented as grid items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM
: the corresponding items are presented as "category" list items. These are the same as ordinary list items except that margins are applied around the items' icons, since the icons look better when they are small. The icons must be tintable vector drawables. This hint is expected to only be provided for browsable items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM
: the corresponding items are presented as "category" grid items. These are the same as ordinary grid items, except that margins are applied around the items' icons, since the icons look better when they are small. The icons must be tintable vector drawables. This hint is expected to only be provided for browsable items.
The following code snippet shows how to set the default content style for browsable items to grids and playable items to lists:
Котлин
import androidx.media.utils.MediaConstants @Nullable override fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) return BrowserRoot(ROOT_ID, extras) }
Ява
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); 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, as well as any media item itself.
To override the default for a browsable media item's children , create an extras bundle in the MediaDescription
of the media item and add the same previously mentioned hints. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
applies to that item's playable children, while DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
applies to that item's browsable children.
To override the default for a particular media item itself , not its children, create an extras bundle in the MediaDescription
of the media item and add a hint with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM
. Use the same values described previously to specify that item's presentation.
The following code snippet shows how to create a browsable MediaItem
that overrides the default content style for both itself and its children. It styles itself as a category list item, its browsable children as list items, and its playable children as grid items:
Котлин
import androidx.media.utils.MediaConstants 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE) }
Ява
import androidx.media.utils.MediaConstants; 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); 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 includes a mapping with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE
and an identical string value. Localize this string, which is used as the title of the group.
The following code snippet shows how to create a MediaItem
with a subgroup heading of "Songs"
:
Котлин
import androidx.media.utils.MediaConstants 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs") mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/) }
Ява
import androidx.media.utils.MediaConstants; 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs"); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/); }
Your app must pass all the media items that you want to group together as a contiguous block. For example, suppose that you want to display two groups of media items, "Songs" and "Albums," in that order, and your app passes in five media items in the following order:
- Media item A with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item B with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
- Media item C with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item D with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item E with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "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 interprets this as the following four groups:
- 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 must pass the media items in the following order instead:
- Media item A with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item C with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item D with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item B with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
- Media item E with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
Display additional metadata indicators
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 look 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.
The following constants can be used in both MediaItem
description extras and MediaMetadata
extras:
-
EXTRA_DOWNLOAD_STATUS
: indicates the download status of an item. Use this constant as the key; the following long constants are the possible values:-
STATUS_DOWNLOADED
: the item is completely downloaded. -
STATUS_DOWNLOADING
: the item is being downloaded. -
STATUS_NOT_DOWNLOADED
: the item is not downloaded.
-
-
METADATA_KEY_IS_EXPLICIT
: indicates whether the item contains explicit content. To indicate an item is explicit, use this constant as the key and the longMETADATA_VALUE_ATTRIBUTE_PRESENT
as the value.
The following constants can only be used in MediaItem
description extras:
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
: indicates the completion state of long-form content, such as podcast episodes or audiobooks. Use this constant as the key; the following integer constants are the possible values:-
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
: the item has not been played at all. -
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
: the item has been partially played, and the current position is somewhere in the middle. -
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
: the item has been completed.
-
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
: indicates the amount of completion progress on long-form content as a double between 0.0 and 1.0, inclusive. This extra provides more information about thePARTIALLY_PLAYING
state so that Android Auto or Android Automotive OS displays a more meaningful progress indicator, such as a progress bar. If you use this extra, see the section about updating the progress bar in the browse view as content is playing in this guide to learn how to keep this indicator up to date after its initial impression.
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 media item that is 70% complete:
Котлин
import androidx.media.utils.MediaConstants val extras = Bundle() extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED) extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7) val description = MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build() return MediaBrowserCompat.MediaItem(description, /* flags */)
Ява
import androidx.media.utils.MediaConstants; Bundle extras = new Bundle(); extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED); extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7); 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 METADATA_KEY_IS_EXPLICIT
or EXTRA_DOWNLOAD_STATUS
in the MediaMetadataCompat
of your mediaSession
. You can't display the DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
or DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
indicators on the playback view.
The following code snippet shows how to indicate that the current song in the playback view is explicit and downloaded:
Котлин
import androidx.media.utils.MediaConstants mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build())
Ява
import androidx.media.utils.MediaConstants; mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build());
Update the progress bar in the browse view as content is playing
As previously mentioned, you can use the DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
extra to show a progress bar for partially played content in the browse view. However, if a user continues playing the partially played content from Android Auto or Android Automotive OS, that indicator becomes inaccurate as time passes.
For Android Auto and Android Automotive OS to keep the progress bar up to date, you can supply additional information in MediaMetadataCompat
and PlaybackStateCompat
to link ongoing content to media items in the browse view. The following requirements must be met for the media item to have an automatically updating progress bar:
- When it is created, the
MediaItem
must sendDESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
in its extras with a value between 0.0 and 1.0, inclusive. - The
MediaMetadataCompat
must sendMETADATA_KEY_MEDIA_ID
with a string value equal to the media ID passed to theMediaItem
. - The
PlaybackStateCompat
must include an extra with the keyPLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID
that maps to a string value equal to the media ID passed to theMediaItem
.
The following code snippet shows how to indicate that the currently playing item is linked to an item in the browse view:
Котлин
import androidx.media.utils.MediaConstants // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. val mediaItemExtras = Bundle() mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25) val description = MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build() return MediaBrowserCompat.MediaItem(description, /* flags */) // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()) val playbackStateExtras = Bundle() playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id") mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build())
Ява
import androidx.media.utils.MediaConstants; // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. Bundle mediaItemExtras = new Bundle(); mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build(); return MediaBrowserCompat.MediaItem(description, /* flags */); // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()); Bundle playbackStateExtras = new Bundle(); playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id"); mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build());
Display browsable search results
Your app can provide contextual search results that display to users when they initiate a search query. Android Auto and Android Automotive OS show these results through search query interfaces or through affordances that pivot on queries made earlier in the session. To learn more, see the Support voice actions section in this guide.
To display browsable search results, include the constant key BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED
in the extras bundle of your service's onGetRoot()
method, mapping to the boolean true
.
The following code snippet shows how to enable support in the onGetRoot()
method:
Котлин
import androidx.media.utils.MediaConstants @Nullable fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true) return BrowserRoot(ROOT_ID, extras) }
Ява
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_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 the user's search terms to this method whenever a user invokes a search query interface or “Search 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:
Котлин
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. }
Ява
@Override public void onSearch(final String query, final Bundle extras, Result<List<MediaItem>> result) { // Detach from results to unblock the caller (if a search is expensive). result.detach(); new AsyncTask<Void, Void, Void>() { List<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); } } }.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. }
Custom Browse Actions
Custom Browse Actions allow you to add custom icons and labels to your app's MediaItem
objects in the car's media app, and handle user interactions with these actions. This lets you extend the functionality of the Media App in a variety of ways, like adding "Download", "Add to Queue", "Play Radio", "Favorite", or "Remove" actions.
If there are more custom actions than the OEM allows to be displayed, an overflow menu will be presented to the user.
Как они работают?
Each Custom Browse Action is defined with:
- An Action ID (a unique string identifier)
- An Action Label (the text displayed to the user)
- An Action Icon URI (a vector drawable that can be tinted)
You define a list of Custom Browse Actions globally as part of your BrowseRoot
. Then you can attach a subset of these actions to individual MediaItem.
When a user interacts with a Custom Browse Action, your app receives a callback in onCustomAction()
. You can then handle the action and update the list of actions for the MediaItem
if necessary. This is useful for stateful actions like "Favorite" and "Download". For actions that don't need updating, like "Play Radio", you don't need to update the list of actions.
You can also attach Custom Browse Actions to a browse node root. These actions will be displayed in a secondary toolbar under the main toolbar.
How to implement Custom Browse Actions
Here are the steps to add Custom Browse Actions to your project:
- Override two methods in your
MediaBrowserServiceCompat
implementation: - Parse the action limits at runtime:
- In
onGetRoot()
, get the maximum number of actions allowed for eachMediaItem
using the keyBROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT
in therootHints
Bundle
. A limit of 0 indicates that the feature is not supported by the system.
- In
- Build the global list of Custom Browse Actions:
- For each action, create a
Bundle
object with the following keys: *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID
: The action ID *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL
: The action label *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI
: The action icon URI * Add all the actionBundle
objects to a list.
- For each action, create a
- Add the global list to your
BrowseRoot
:- In the
BrowseRoot
extrasBundle
, add the list of actions as aParcelable
Arraylist
using the keyBROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
.
- In the
- Add actions to your
MediaItem
objects:- You can add actions to individual
MediaItem
objects by including the list of action IDs in theMediaDescriptionCompat
extras using the keyDESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
. This list must be a subset of the global list of actions you defined in theBrowseRoot
.
- You can add actions to individual
- Handle actions and return progress or results:
- In
onCustomAction
, handle the action based on the action ID and any other data you need. You can get the ID of theMediaItem
that triggered the action from the extras using the keyEXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID
. . - You can update the list of actions for a
MediaItem
by including the keyEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
in the progress or result bundle.
- In
Here are some changes you can make in your BrowserServiceCompat
to get started with Custom Browse Actions.
Override BrowserServiceCompat
You need to override the following methods in MediaBrowserServiceCompat
.
public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)
public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)
Parse actions limit
You should check to see how many Custom Browse Actions are supported.
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0) }
Build a Custom Browse Action
Each action needs to be packed into a separate Bundle
.
- Action ID
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, "<ACTION_ID>")
- Action Label
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, "<ACTION_LABEL>")
- Action Icon URI
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, "<ACTION_ICON_URI>")
Add Custom Browse Actions to Parceable
ArrayList
Add all Custom Browse Action Bundle
objects into an ArrayList
.
private ArrayList<Bundle> createCustomActionsList( CustomBrowseAction browseActions) { ArrayList<Bundle> browseActionsBundle = new ArrayList<>(); for (CustomBrowseAction browseAction : browseActions) { Bundle action = new Bundle(); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, browseAction.mId); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, getString(browseAction.mLabelResId)); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, browseAction.mIcon); browseActionsBundle.add(action); } return browseActionsBundle; }
Add Custom Browse Action list to the browse root
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { Bundle browserRootExtras = new Bundle(); browserRootExtras.putParcelableArrayList( BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST, createCustomActionsList())); mRoot = new BrowserRoot(ROOT_ID, browserRootExtras); return mRoot; }
Add actions to a MediaItem
MediaDescriptionCompat buildDescription (long id, String title, String subtitle, String description, Uri iconUri, Uri mediaUri, ArrayList<String> browseActionIds) { MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder(); bob.setMediaId(id); bob.setTitle(title); bob.setSubtitle(subtitle); bob.setDescription(description); bob.setIconUri(iconUri); bob.setMediaUri(mediaUri); Bundle extras = new Bundle(); extras.putStringArrayList( DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST, browseActionIds); bob.setExtras(extras); return bob.build(); } MediaItem mediaItem = new MediaItem(buildDescription(...), flags);
Build onCustomAction
result
- Parse mediaId from
Bundle extras
:@Override public void onCustomAction( @NonNull String action, Bundle extras, @NonNull Result<Bundle> result){ String mediaId = extras.getString(MediaConstans.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID); }
- For asynchronous results detach result.
result.detach()
- Build result bundle
- Сообщение пользователю
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE, mContext.getString(stringRes))
- Update item(use to update actions in an item)
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
- Open Playback view
//Shows user the PBV without changing the playback state mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
- Update Browse Node
//Change current browse node to mediaId mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
- Сообщение пользователю
- If an error, call
result.sendError(resultBundle).
- If progress update, call
result.sendProgressUpdate(resultBundle)
. - Finish by calling
result.sendResult(resultBundle)
.
Update Action State
By using the result.sendProgressUpdate(resultBundle)
method with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key, you can update the MediaItem
to reflect the new state of the action. This lets you provide real-time feedback to the user about the progress and result of their action.
Example: Download Action
Here's an example of how you can use this feature to implement a download action with three states:
- Download: This is the initial state of the action. When the user selects this action, you can swap it with "Downloading" and call
sendProgressUpdate
to update the UI. - Downloading: This state indicates that the download is in progress. You can use this state to show a progress bar or another indicator to the user.
- Downloaded: This state indicates that the download is complete. When the download finishes, you can swap "Downloading" with "Downloaded" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to indicate that the item should be refreshed. Additionally, you can use theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE
key to display a success message to the user.
This approach lets you provide clear feedback to the user about the download process and its current state. You can add even more detail with icons to show 25%, 50%, 75% download states.
Example: Favorite Action
Another example is a favorite action with two states:
- Favorite: This action is displayed for items that are not in the user's favorites list. When the user selects this action, you can swap it with "Favorited" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to update the UI. - Favorited: This action is displayed for items that are in the user's favorites list. When the user selects this action, you can swap it with "Favorite" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to update the UI.
This approach provides a clear and consistent way for users to manage their favorite items.
These examples showcase the flexibility of Custom Browse Actions and how you can use them to implement a variety of functionalities with real-time feedback for an enhanced user experience in the car's media app.
For a complete example implementation of this feature, you can refer to the TestMediaApp
project.
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:
Котлин
override fun onCreate() { super.onCreate() ... // Start a new MediaSession. val session = MediaSessionCompat(this, "session tag").apply { // Set a callback object that implements MediaSession.Callback // to handle play control requests. setCallback(MyMediaSessionCallback()) } sessionToken = session.sessionToken ... }
Ява
public void onCreate() { super.onCreate(); ... // Start a new MediaSession. MediaSessionCompat session = new MediaSessionCompat(this, "session tag"); setSessionToken(session.getSessionToken()); // Set a callback object that implements MediaSession.Callback // to handle play control requests. 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.
Implement all 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 must play its default content or, if playback was paused with
onPause()
, your app resumes 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 the section about setting the 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 must 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.
Override these methods in your app to provide any desired functionality. You don't need to implement a method if its functionality isn't supported by your app. For example, if your app plays a live stream, such as a sports broadcast, you don't need to implement the onSkipToNext()
method. You can use the default implementation of onSkipToNext()
instead.
Your app doesn't need any special logic to play content through the car's speakers. When your app receives a request to play content, it can play audio the same way that it plays 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 MediaPlayer overview , Audio app overview , and the ExoPlayer overview .
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:
Your app can additionally support the following actions if they are relevant to the app's content:
In addition, you have the option to create a play queue that can be displayed for the user, but it is not required. To do this, call the setQueue()
and setQueueTitle()
methods, enable the ACTION_SKIP_TO_QUEUE_ITEM
action, and define the callback onSkipToQueueItem()
.
Also, add support for the Now playing icon, which is an indicator for what is currently playing. To do this, call the setActiveQueueItemId()
method and pass the ID of the currently playing item in the queue. You need to update setActiveQueueItemId()
whenever there is a queue change.
Android Auto and Android Automotive OS display buttons for each enabled action as well as the playback queue. When the buttons are clicked, the system invokes their corresponding callback from MediaSessionCompat.Callback
.
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. 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 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 the reserved functions. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
corresponds to ACTION_SKIP_TO_NEXT
, and SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
corresponds to ACTION_SKIP_TO_PREVIOUS
. Use these constants as keys in the bundle, and use the boolean true
for their values.
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 PlaybackStateCompat
. 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 PlaybackStateCompat
of your media session to STATE_STOPPED
, STATE_PAUSED
, STATE_NONE
, or STATE_ERROR
.
Media sessions within Android Auto and Android Automotive OS only last for the duration of the drive, so users start and stop these sessions frequently. To promote a seamless experience between drives, keep track of the user's previous session state, so that when the media app receives a resume request, the user can automatically pick up where they left off—for example, the last played media item, the PlaybackStateCompat
, and the queue.
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 display in the overflow menu. Custom actions display in the order they are added to the PlaybackStateCompat
.
Use custom actions to provide behavior distinct from standard actions . Don't use them to replace or duplicate standard actions.
You can add custom 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:
Котлин
stateBuilder.addCustomAction( PlaybackStateCompat.CustomAction.Builder( CUSTOM_ACTION_START_RADIO_FROM_MEDIA, resources.getString(R.string.start_radio_from_media), startRadioFromMediaIcon ).run { setExtras(customActionExtras) build() } )
Ява
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 might respond to a “Start a radio channel” action:
Котлин
override fun onCustomAction(action: String, extras: Bundle?) { when(action) { CUSTOM_ACTION_START_RADIO_FROM_MEDIA -> { ... } } }
Ява
@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.
If a custom action is stateful—for example, it toggles a playback setting on or off—provide different icons for the different states, so users can see a change when they select the action.
Provide alternative icon styles for disabled actions
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.
Indicate audio format
To indicate that currently playing media uses a special audio format, you can specify icons that are rendered in cars that support this feature. You can set the KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI
and the KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI
in the extras bundle of the currently playing media item (passed to MediaSession.setMetadata()
). Make sure to set both of those extras, to accommodate different layouts.
In addition, you can set the KEY_IMMERSIVE_AUDIO
extra to tell car OEMs that this is immersive audio, and they should be very careful when deciding whether to apply audio effects that might interfere with the immersive content.
Add links from currently-playing item
You can configure the currently-playing media item so its subtitle, description, or both are links to other media items. That lets the user jump quickly to related items; for example, they might jump to other songs by the same artist, other episodes of that podcast, etc. If the car supports this feature, users can tap the link to browse to that content.
To add links, configure the KEY_SUBTITLE_LINK_MEDIA_ID
metadata (to link from the subtitle) or KEY_DESCRIPTION_LINK_MEDIA_ID
(to link from the description). For details, see the reference documentation for those metadata fields.
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 playing one media item, the user can say “ Play [song title] " to tell your app to play a different song without looking at or touching the car's display. Users can initiate queries by clicking the appropriate buttons on their steering wheel or speaking the hotwords " OK Google ."
When Android Auto or Android Automotive OS detects and interprets a voice action, that voice action is delivered to the app through onPlayFromSearch()
. On receiving this callback, the app finds content matching the query
string and starts playback.
Users can specify different categories of terms in their query: genre, artist, album, song name, radio station, or playlist, among others. When building support for search, account for all the categories that make sense for your app. If Android Auto or Android Automotive OS detects that a given query fits into certain categories, it appends extras in the extras
parameter. The following extras can be sent:
Account for an empty query
string, which can be sent by Android Auto or Android Automotive OS if the user doesn't specify search terms. For example, if the user says " Play some music ." In that case, your app might choose to start a recently played or newly suggested track.
If a search cannot be processed quickly, do not block in onPlayFromSearch()
. Instead, set the playback state to STATE_CONNECTING
and perform the search on an async thread.
Once playback begins, consider populating the media session's queue with related content. For example, if the user requests an album to be played, your app might fill the queue with the album's tracklist. Also consider implementing support for browsable search results so a user can choose a different track that matches their query.
In addition to " play " queries, Android Auto and Android Automotive OS recognize voice queries to control playback like " pause music " and " next song " and match these commands to the appropriate media session callbacks, like onPause()
and onSkipToNext()
.
For a detailed example on how to implement voice-enabled playback actions in your app, see 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.
Suppress alarms in the car
Android Auto media apps must not start playing audio through the car speakers unless the user starts playback by, for example, pressing a play button. Even a user-scheduled alarm from your media app must not start playing music through the car speakers.
To fulfill this requirement, your app can use CarConnection
as a signal before playing any audio. Your app can check whether the phone is projecting to a car screen by observing the LiveData
for the car connection type and checking whether it is equal to CONNECTION_TYPE_PROJECTION
.
If the user's phone is projecting, 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.
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 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 METADATA_KEY_IS_ADVERTISEMENT
to METADATA_VALUE_ATTRIBUTE_PRESENT
, as shown in the following code snippet:
Котлин
import androidx.media.utils.MediaConstants override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) { MediaMetadataCompat.Builder().apply { if (isAd(mediaId)) { putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) } // ...add any other properties you normally would. mediaSession.setMetadata(build()) } }
Ява
import androidx.media.utils.MediaConstants; @Override public void onPlayFromMediaId(String mediaId, Bundle extras) { MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); if (isAd(mediaId)) { builder.putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); } // ...add any other properties you normally would. mediaSession.setMetadata(builder.build()); }
Handle general errors
When the app experiences an error, set the playback state to STATE_ERROR
and provide an error message using the setErrorMessage()
method. See PlaybackStateCompat
for a list of error codes that you can use when setting the error message. 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 example, if content is not available in the user's current region, you can use the ERROR_CODE_NOT_AVAILABLE_IN_REGION
error code when setting the error message.
Котлин
mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build())
Ява
mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build());
For more information about error states, see Using a media session: States and errors .
If an Android Auto user needs to open your phone app to resolve an error, provide that information to the user in your message. For example, your error message might say "Sign in to [your app name]" instead of "Please sign in."
Другие ресурсы
,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.
Прежде чем начать
- Review the Android media API documentation .
- Review Create media apps for design guidance.
- Review the key terms and concepts listed in this section.
Ключевые термины и понятия
- 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 the following flags:-
FLAG_PLAYABLE
: 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. -
FLAG_BROWSABLE
: indicates that 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 to browse media items. Declare your media browser service in your manifest to let Android Auto and Android Automotive OS discover the service and connect to your app.
The following code snippet shows how to declare your media browser service in your manifest. 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 app icons
You need to specify app icons that Android Auto and Android Automotive OS can use to represent your app in the system UI. Two icon types are required:
- Значок запуска
- Attribution icon
Значок запуска
The launcher icon represents your app in the system UI, such as on the launcher and in the tray of icons. You can specify that you want to use the icon from your mobile app to represent your car media app using the following manifest declaration:
<application
...
android:icon="@mipmap/ic_launcher"
...
/>
To use a different icon than your mobile app's, set the android:icon
property on your media browser service's <service>
element in the manifest:
<application>
...
<service
...
android:icon="@mipmap/auto_launcher"
...
/>
</application>
Attribution icon
The attribution icon is used in places where media content takes precedence, such as on media cards. Consider reusing the small icon used for notifications. This icon must be monochrome. You can specify an icon that is used to represent your app using the following manifest declaration:
<application>
...
<meta-data
android:name="androidx.car.app.TintableAttributionIcon"
android:resource="@drawable/ic_status_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 to present a menu to the user.
- Get the token for your app's
MediaSessionCompat
object to control audio playback.
You can also use your media browser service to let other clients access media content from your app. These media clients might be other apps on a user's phone, or they can be other remote clients.
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.
- The 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. See Structure the root menu on this page for more information on what the system expects at this level. - 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:
Котлин
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)
Ява
@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 whether 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 allowlist 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.
To provide system apps, such as Android Auto and Android Automotive OS, with access to your content, your service must always return a non-null BrowserRoot
when these system apps call the onGetRoot()
method. The signature of the Android Automotive OS system app can vary depending on the make and model of the car, so you need to permit connections from all system apps to support Android Automotive OS robustly.
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.
In addition to allowing system apps, you must let the Google Assistant connect to your MediaBrowserService
. Note that the Google Assistant has separate package names for the phone, which includes Android Auto, and for Android Automotive OS.
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.
The following code snippet shows a simple implementation of onLoadChildren()
method:
Котлин
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 whether 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) }
Ява
@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 whether 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.
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()
. Following these hints lets the system display the root content optimally as navigational tabs. If you don't follow these hints, some root content might be dropped or made less discoverable by the system. Two hints are sent:
- A limit on the number of root children : for the majority of cases, you can expect this number to be four. This means that over four tabs cannot be shown.
- Supported flags on the root children : you can expect this value to be
MediaItem#FLAG_BROWSABLE
. This means that only browsable items—not playable items—can be shown as tabs.
Use the following code to read the relevant root hints:
Котлин
import androidx.media.utils.MediaConstants // Later, in your MediaBrowserServiceCompat. 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... }
Ява
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 Android Automotive OS. 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 the root hints, there are a couple additional guidelines to follow to help ensure that tabs render optimally:
- Supply monochrome, preferably white, icons for each tab item.
- Supply short but meaningful labels for each tab item. Keeping labels short reduces the chance of the strings being truncated.
Display media artwork
Artwork for media items must be passed as a local URI using either ContentResolver.SCHEME_CONTENT
or ContentResolver.SCHEME_ANDROID_RESOURCE
. This local URI must resolve to either a bitmap or a vector drawable in the application's resources. For MediaDescriptionCompat
objects representing items in the content hierarchy, pass the URI through setIconUri()
. For MediaMetadataCompat
objects representing the currently playing item, pass the URI through putString()
, using any of the following keys:
-
MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI
-
MediaMetadataCompat.METADATA_KEY_ART_URI
-
MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI
The following steps describe how to download art from a web URI and expose it through a local URI. For a more complete example, see the implementation of openFile()
and the surrounding methods in the Universal Android Music Player sample app.
Build a
content://
URI corresponding to the web URI. The media browser service and media session pass this content URI to Android Auto and Android Automotive OS.Котлин
fun Uri.asAlbumArtContentURI(): Uri { return Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(this.getPath()) // Make sure you trust the URI .build() }
Ява
public static Uri asAlbumArtContentURI(Uri webUri) { return new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(webUri.getPath()) // Make sure you trust the URI! .build(); }
In your implementation of
ContentProvider.openFile()
, check whether a file exists for the corresponding URI. If not, download and cache the image file. The following code snippet uses Glide .Котлин
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? { val context = this.context ?: return null val file = File(context.cacheDir, uri.path) if (!file.exists()) { val remoteUri = Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.path) .build() val cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS) cacheFile.renameTo(file) file = cacheFile } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) }
Ява
@Nullable @Override public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { Context context = this.getContext(); File file = new File(context.getCacheDir(), uri.getPath()); if (!file.exists()) { Uri remoteUri = new Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.getPath()) .build(); File cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS); cacheFile.renameTo(file); file = cacheFile; } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); }
For more details about content providers, refer to Creating a content provider .
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:
- Список элементов
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 this bundle and look for those constants to determine the appropriate style.
The following extras can be used as keys in the bundle:
-
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
: indicates a presentation hint for all browsable items within the browse tree. -
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
: indicates a presentation hint for all playable items within the browse tree.
The keys can map to the following integer constant values to influence the presentation of those items:
-
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
: the corresponding items are presented as list items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
: the corresponding items are presented as grid items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM
: the corresponding items are presented as "category" list items. These are the same as ordinary list items except that margins are applied around the items' icons, since the icons look better when they are small. The icons must be tintable vector drawables. This hint is expected to only be provided for browsable items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM
: the corresponding items are presented as "category" grid items. These are the same as ordinary grid items, except that margins are applied around the items' icons, since the icons look better when they are small. The icons must be tintable vector drawables. This hint is expected to only be provided for browsable items.
The following code snippet shows how to set the default content style for browsable items to grids and playable items to lists:
Котлин
import androidx.media.utils.MediaConstants @Nullable override fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) return BrowserRoot(ROOT_ID, extras) }
Ява
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); 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, as well as any media item itself.
To override the default for a browsable media item's children , create an extras bundle in the MediaDescription
of the media item and add the same previously mentioned hints. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
applies to that item's playable children, while DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
applies to that item's browsable children.
To override the default for a particular media item itself , not its children, create an extras bundle in the MediaDescription
of the media item and add a hint with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM
. Use the same values described previously to specify that item's presentation.
The following code snippet shows how to create a browsable MediaItem
that overrides the default content style for both itself and its children. It styles itself as a category list item, its browsable children as list items, and its playable children as grid items:
Котлин
import androidx.media.utils.MediaConstants 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE) }
Ява
import androidx.media.utils.MediaConstants; 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); 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 includes a mapping with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE
and an identical string value. Localize this string, which is used as the title of the group.
The following code snippet shows how to create a MediaItem
with a subgroup heading of "Songs"
:
Котлин
import androidx.media.utils.MediaConstants 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs") mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/) }
Ява
import androidx.media.utils.MediaConstants; 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs"); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/); }
Your app must pass all the media items that you want to group together as a contiguous block. For example, suppose that you want to display two groups of media items, "Songs" and "Albums," in that order, and your app passes in five media items in the following order:
- Media item A with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item B with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
- Media item C with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item D with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item E with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "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 interprets this as the following four groups:
- 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 must pass the media items in the following order instead:
- Media item A with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item C with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item D with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item B with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
- Media item E with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
Display additional metadata indicators
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 look 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.
The following constants can be used in both MediaItem
description extras and MediaMetadata
extras:
-
EXTRA_DOWNLOAD_STATUS
: indicates the download status of an item. Use this constant as the key; the following long constants are the possible values:-
STATUS_DOWNLOADED
: the item is completely downloaded. -
STATUS_DOWNLOADING
: the item is being downloaded. -
STATUS_NOT_DOWNLOADED
: the item is not downloaded.
-
-
METADATA_KEY_IS_EXPLICIT
: indicates whether the item contains explicit content. To indicate an item is explicit, use this constant as the key and the longMETADATA_VALUE_ATTRIBUTE_PRESENT
as the value.
The following constants can only be used in MediaItem
description extras:
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
: indicates the completion state of long-form content, such as podcast episodes or audiobooks. Use this constant as the key; the following integer constants are the possible values:-
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
: the item has not been played at all. -
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
: the item has been partially played, and the current position is somewhere in the middle. -
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
: the item has been completed.
-
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
: indicates the amount of completion progress on long-form content as a double between 0.0 and 1.0, inclusive. This extra provides more information about thePARTIALLY_PLAYING
state so that Android Auto or Android Automotive OS displays a more meaningful progress indicator, such as a progress bar. If you use this extra, see the section about updating the progress bar in the browse view as content is playing in this guide to learn how to keep this indicator up to date after its initial impression.
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 media item that is 70% complete:
Котлин
import androidx.media.utils.MediaConstants val extras = Bundle() extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED) extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7) val description = MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build() return MediaBrowserCompat.MediaItem(description, /* flags */)
Ява
import androidx.media.utils.MediaConstants; Bundle extras = new Bundle(); extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED); extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7); 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 METADATA_KEY_IS_EXPLICIT
or EXTRA_DOWNLOAD_STATUS
in the MediaMetadataCompat
of your mediaSession
. You can't display the DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
or DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
indicators on the playback view.
The following code snippet shows how to indicate that the current song in the playback view is explicit and downloaded:
Котлин
import androidx.media.utils.MediaConstants mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build())
Ява
import androidx.media.utils.MediaConstants; mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build());
Update the progress bar in the browse view as content is playing
As previously mentioned, you can use the DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
extra to show a progress bar for partially played content in the browse view. However, if a user continues playing the partially played content from Android Auto or Android Automotive OS, that indicator becomes inaccurate as time passes.
For Android Auto and Android Automotive OS to keep the progress bar up to date, you can supply additional information in MediaMetadataCompat
and PlaybackStateCompat
to link ongoing content to media items in the browse view. The following requirements must be met for the media item to have an automatically updating progress bar:
- When it is created, the
MediaItem
must sendDESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
in its extras with a value between 0.0 and 1.0, inclusive. - The
MediaMetadataCompat
must sendMETADATA_KEY_MEDIA_ID
with a string value equal to the media ID passed to theMediaItem
. - The
PlaybackStateCompat
must include an extra with the keyPLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID
that maps to a string value equal to the media ID passed to theMediaItem
.
The following code snippet shows how to indicate that the currently playing item is linked to an item in the browse view:
Котлин
import androidx.media.utils.MediaConstants // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. val mediaItemExtras = Bundle() mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25) val description = MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build() return MediaBrowserCompat.MediaItem(description, /* flags */) // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()) val playbackStateExtras = Bundle() playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id") mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build())
Ява
import androidx.media.utils.MediaConstants; // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. Bundle mediaItemExtras = new Bundle(); mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build(); return MediaBrowserCompat.MediaItem(description, /* flags */); // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()); Bundle playbackStateExtras = new Bundle(); playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id"); mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build());
Display browsable search results
Your app can provide contextual search results that display to users when they initiate a search query. Android Auto and Android Automotive OS show these results through search query interfaces or through affordances that pivot on queries made earlier in the session. To learn more, see the Support voice actions section in this guide.
To display browsable search results, include the constant key BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED
in the extras bundle of your service's onGetRoot()
method, mapping to the boolean true
.
The following code snippet shows how to enable support in the onGetRoot()
method:
Котлин
import androidx.media.utils.MediaConstants @Nullable fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true) return BrowserRoot(ROOT_ID, extras) }
Ява
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_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 the user's search terms to this method whenever a user invokes a search query interface or “Search 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:
Котлин
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. }
Ява
@Override public void onSearch(final String query, final Bundle extras, Result<List<MediaItem>> result) { // Detach from results to unblock the caller (if a search is expensive). result.detach(); new AsyncTask<Void, Void, Void>() { List<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); } } }.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. }
Custom Browse Actions
Custom Browse Actions allow you to add custom icons and labels to your app's MediaItem
objects in the car's media app, and handle user interactions with these actions. This lets you extend the functionality of the Media App in a variety of ways, like adding "Download", "Add to Queue", "Play Radio", "Favorite", or "Remove" actions.
If there are more custom actions than the OEM allows to be displayed, an overflow menu will be presented to the user.
Как они работают?
Each Custom Browse Action is defined with:
- An Action ID (a unique string identifier)
- An Action Label (the text displayed to the user)
- An Action Icon URI (a vector drawable that can be tinted)
You define a list of Custom Browse Actions globally as part of your BrowseRoot
. Then you can attach a subset of these actions to individual MediaItem.
When a user interacts with a Custom Browse Action, your app receives a callback in onCustomAction()
. You can then handle the action and update the list of actions for the MediaItem
if necessary. This is useful for stateful actions like "Favorite" and "Download". For actions that don't need updating, like "Play Radio", you don't need to update the list of actions.
You can also attach Custom Browse Actions to a browse node root. These actions will be displayed in a secondary toolbar under the main toolbar.
How to implement Custom Browse Actions
Here are the steps to add Custom Browse Actions to your project:
- Override two methods in your
MediaBrowserServiceCompat
implementation: - Parse the action limits at runtime:
- In
onGetRoot()
, get the maximum number of actions allowed for eachMediaItem
using the keyBROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT
in therootHints
Bundle
. A limit of 0 indicates that the feature is not supported by the system.
- In
- Build the global list of Custom Browse Actions:
- For each action, create a
Bundle
object with the following keys: *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID
: The action ID *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL
: The action label *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI
: The action icon URI * Add all the actionBundle
objects to a list.
- For each action, create a
- Add the global list to your
BrowseRoot
:- In the
BrowseRoot
extrasBundle
, add the list of actions as aParcelable
Arraylist
using the keyBROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
.
- In the
- Add actions to your
MediaItem
objects:- You can add actions to individual
MediaItem
objects by including the list of action IDs in theMediaDescriptionCompat
extras using the keyDESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
. This list must be a subset of the global list of actions you defined in theBrowseRoot
.
- You can add actions to individual
- Handle actions and return progress or results:
- In
onCustomAction
, handle the action based on the action ID and any other data you need. You can get the ID of theMediaItem
that triggered the action from the extras using the keyEXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID
. . - You can update the list of actions for a
MediaItem
by including the keyEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
in the progress or result bundle.
- In
Here are some changes you can make in your BrowserServiceCompat
to get started with Custom Browse Actions.
Override BrowserServiceCompat
You need to override the following methods in MediaBrowserServiceCompat
.
public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)
public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)
Parse actions limit
You should check to see how many Custom Browse Actions are supported.
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0) }
Build a Custom Browse Action
Each action needs to be packed into a separate Bundle
.
- Action ID
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, "<ACTION_ID>")
- Action Label
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, "<ACTION_LABEL>")
- Action Icon URI
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, "<ACTION_ICON_URI>")
Add Custom Browse Actions to Parceable
ArrayList
Add all Custom Browse Action Bundle
objects into an ArrayList
.
private ArrayList<Bundle> createCustomActionsList( CustomBrowseAction browseActions) { ArrayList<Bundle> browseActionsBundle = new ArrayList<>(); for (CustomBrowseAction browseAction : browseActions) { Bundle action = new Bundle(); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, browseAction.mId); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, getString(browseAction.mLabelResId)); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, browseAction.mIcon); browseActionsBundle.add(action); } return browseActionsBundle; }
Add Custom Browse Action list to the browse root
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { Bundle browserRootExtras = new Bundle(); browserRootExtras.putParcelableArrayList( BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST, createCustomActionsList())); mRoot = new BrowserRoot(ROOT_ID, browserRootExtras); return mRoot; }
Add actions to a MediaItem
MediaDescriptionCompat buildDescription (long id, String title, String subtitle, String description, Uri iconUri, Uri mediaUri, ArrayList<String> browseActionIds) { MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder(); bob.setMediaId(id); bob.setTitle(title); bob.setSubtitle(subtitle); bob.setDescription(description); bob.setIconUri(iconUri); bob.setMediaUri(mediaUri); Bundle extras = new Bundle(); extras.putStringArrayList( DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST, browseActionIds); bob.setExtras(extras); return bob.build(); } MediaItem mediaItem = new MediaItem(buildDescription(...), flags);
Build onCustomAction
result
- Parse mediaId from
Bundle extras
:@Override public void onCustomAction( @NonNull String action, Bundle extras, @NonNull Result<Bundle> result){ String mediaId = extras.getString(MediaConstans.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID); }
- For asynchronous results detach result.
result.detach()
- Build result bundle
- Сообщение пользователю
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE, mContext.getString(stringRes))
- Update item(use to update actions in an item)
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
- Open Playback view
//Shows user the PBV without changing the playback state mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
- Update Browse Node
//Change current browse node to mediaId mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
- Сообщение пользователю
- If an error, call
result.sendError(resultBundle).
- If progress update, call
result.sendProgressUpdate(resultBundle)
. - Finish by calling
result.sendResult(resultBundle)
.
Update Action State
By using the result.sendProgressUpdate(resultBundle)
method with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key, you can update the MediaItem
to reflect the new state of the action. This lets you provide real-time feedback to the user about the progress and result of their action.
Example: Download Action
Here's an example of how you can use this feature to implement a download action with three states:
- Download: This is the initial state of the action. When the user selects this action, you can swap it with "Downloading" and call
sendProgressUpdate
to update the UI. - Downloading: This state indicates that the download is in progress. You can use this state to show a progress bar or another indicator to the user.
- Downloaded: This state indicates that the download is complete. When the download finishes, you can swap "Downloading" with "Downloaded" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to indicate that the item should be refreshed. Additionally, you can use theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE
key to display a success message to the user.
This approach lets you provide clear feedback to the user about the download process and its current state. You can add even more detail with icons to show 25%, 50%, 75% download states.
Example: Favorite Action
Another example is a favorite action with two states:
- Favorite: This action is displayed for items that are not in the user's favorites list. When the user selects this action, you can swap it with "Favorited" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to update the UI. - Favorited: This action is displayed for items that are in the user's favorites list. When the user selects this action, you can swap it with "Favorite" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to update the UI.
This approach provides a clear and consistent way for users to manage their favorite items.
These examples showcase the flexibility of Custom Browse Actions and how you can use them to implement a variety of functionalities with real-time feedback for an enhanced user experience in the car's media app.
For a complete example implementation of this feature, you can refer to the TestMediaApp
project.
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:
Котлин
override fun onCreate() { super.onCreate() ... // Start a new MediaSession. val session = MediaSessionCompat(this, "session tag").apply { // Set a callback object that implements MediaSession.Callback // to handle play control requests. setCallback(MyMediaSessionCallback()) } sessionToken = session.sessionToken ... }
Ява
public void onCreate() { super.onCreate(); ... // Start a new MediaSession. MediaSessionCompat session = new MediaSessionCompat(this, "session tag"); setSessionToken(session.getSessionToken()); // Set a callback object that implements MediaSession.Callback // to handle play control requests. 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.
Implement all 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 must play its default content or, if playback was paused with
onPause()
, your app resumes 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 the section about setting the 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 must 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.
Override these methods in your app to provide any desired functionality. You don't need to implement a method if its functionality isn't supported by your app. For example, if your app plays a live stream, such as a sports broadcast, you don't need to implement the onSkipToNext()
method. You can use the default implementation of onSkipToNext()
instead.
Your app doesn't need any special logic to play content through the car's speakers. When your app receives a request to play content, it can play audio the same way that it plays 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 MediaPlayer overview , Audio app overview , and the ExoPlayer overview .
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:
Your app can additionally support the following actions if they are relevant to the app's content:
In addition, you have the option to create a play queue that can be displayed for the user, but it is not required. To do this, call the setQueue()
and setQueueTitle()
methods, enable the ACTION_SKIP_TO_QUEUE_ITEM
action, and define the callback onSkipToQueueItem()
.
Also, add support for the Now playing icon, which is an indicator for what is currently playing. To do this, call the setActiveQueueItemId()
method and pass the ID of the currently playing item in the queue. You need to update setActiveQueueItemId()
whenever there is a queue change.
Android Auto and Android Automotive OS display buttons for each enabled action as well as the playback queue. When the buttons are clicked, the system invokes their corresponding callback from MediaSessionCompat.Callback
.
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. 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 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 the reserved functions. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
corresponds to ACTION_SKIP_TO_NEXT
, and SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
corresponds to ACTION_SKIP_TO_PREVIOUS
. Use these constants as keys in the bundle, and use the boolean true
for their values.
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 PlaybackStateCompat
. 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 PlaybackStateCompat
of your media session to STATE_STOPPED
, STATE_PAUSED
, STATE_NONE
, or STATE_ERROR
.
Media sessions within Android Auto and Android Automotive OS only last for the duration of the drive, so users start and stop these sessions frequently. To promote a seamless experience between drives, keep track of the user's previous session state, so that when the media app receives a resume request, the user can automatically pick up where they left off—for example, the last played media item, the PlaybackStateCompat
, and the queue.
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 display in the overflow menu. Custom actions display in the order they are added to the PlaybackStateCompat
.
Use custom actions to provide behavior distinct from standard actions . Don't use them to replace or duplicate standard actions.
You can add custom 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:
Котлин
stateBuilder.addCustomAction( PlaybackStateCompat.CustomAction.Builder( CUSTOM_ACTION_START_RADIO_FROM_MEDIA, resources.getString(R.string.start_radio_from_media), startRadioFromMediaIcon ).run { setExtras(customActionExtras) build() } )
Ява
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 might respond to a “Start a radio channel” action:
Котлин
override fun onCustomAction(action: String, extras: Bundle?) { when(action) { CUSTOM_ACTION_START_RADIO_FROM_MEDIA -> { ... } } }
Ява
@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.
If a custom action is stateful—for example, it toggles a playback setting on or off—provide different icons for the different states, so users can see a change when they select the action.
Provide alternative icon styles for disabled actions
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.
Indicate audio format
To indicate that currently playing media uses a special audio format, you can specify icons that are rendered in cars that support this feature. You can set the KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI
and the KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI
in the extras bundle of the currently playing media item (passed to MediaSession.setMetadata()
). Make sure to set both of those extras, to accommodate different layouts.
In addition, you can set the KEY_IMMERSIVE_AUDIO
extra to tell car OEMs that this is immersive audio, and they should be very careful when deciding whether to apply audio effects that might interfere with the immersive content.
Add links from currently-playing item
You can configure the currently-playing media item so its subtitle, description, or both are links to other media items. That lets the user jump quickly to related items; for example, they might jump to other songs by the same artist, other episodes of that podcast, etc. If the car supports this feature, users can tap the link to browse to that content.
To add links, configure the KEY_SUBTITLE_LINK_MEDIA_ID
metadata (to link from the subtitle) or KEY_DESCRIPTION_LINK_MEDIA_ID
(to link from the description). For details, see the reference documentation for those metadata fields.
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 playing one media item, the user can say “ Play [song title] " to tell your app to play a different song without looking at or touching the car's display. Users can initiate queries by clicking the appropriate buttons on their steering wheel or speaking the hotwords " OK Google ."
When Android Auto or Android Automotive OS detects and interprets a voice action, that voice action is delivered to the app through onPlayFromSearch()
. On receiving this callback, the app finds content matching the query
string and starts playback.
Users can specify different categories of terms in their query: genre, artist, album, song name, radio station, or playlist, among others. When building support for search, account for all the categories that make sense for your app. If Android Auto or Android Automotive OS detects that a given query fits into certain categories, it appends extras in the extras
parameter. The following extras can be sent:
Account for an empty query
string, which can be sent by Android Auto or Android Automotive OS if the user doesn't specify search terms. For example, if the user says " Play some music ." In that case, your app might choose to start a recently played or newly suggested track.
If a search cannot be processed quickly, do not block in onPlayFromSearch()
. Instead, set the playback state to STATE_CONNECTING
and perform the search on an async thread.
Once playback begins, consider populating the media session's queue with related content. For example, if the user requests an album to be played, your app might fill the queue with the album's tracklist. Also consider implementing support for browsable search results so a user can choose a different track that matches their query.
In addition to " play " queries, Android Auto and Android Automotive OS recognize voice queries to control playback like " pause music " and " next song " and match these commands to the appropriate media session callbacks, like onPause()
and onSkipToNext()
.
For a detailed example on how to implement voice-enabled playback actions in your app, see 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.
Suppress alarms in the car
Android Auto media apps must not start playing audio through the car speakers unless the user starts playback by, for example, pressing a play button. Even a user-scheduled alarm from your media app must not start playing music through the car speakers.
To fulfill this requirement, your app can use CarConnection
as a signal before playing any audio. Your app can check whether the phone is projecting to a car screen by observing the LiveData
for the car connection type and checking whether it is equal to CONNECTION_TYPE_PROJECTION
.
If the user's phone is projecting, 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.
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 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 METADATA_KEY_IS_ADVERTISEMENT
to METADATA_VALUE_ATTRIBUTE_PRESENT
, as shown in the following code snippet:
Котлин
import androidx.media.utils.MediaConstants override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) { MediaMetadataCompat.Builder().apply { if (isAd(mediaId)) { putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) } // ...add any other properties you normally would. mediaSession.setMetadata(build()) } }
Ява
import androidx.media.utils.MediaConstants; @Override public void onPlayFromMediaId(String mediaId, Bundle extras) { MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); if (isAd(mediaId)) { builder.putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); } // ...add any other properties you normally would. mediaSession.setMetadata(builder.build()); }
Handle general errors
When the app experiences an error, set the playback state to STATE_ERROR
and provide an error message using the setErrorMessage()
method. See PlaybackStateCompat
for a list of error codes that you can use when setting the error message. 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 example, if content is not available in the user's current region, you can use the ERROR_CODE_NOT_AVAILABLE_IN_REGION
error code when setting the error message.
Котлин
mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build())
Ява
mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build());
For more information about error states, see Using a media session: States and errors .
If an Android Auto user needs to open your phone app to resolve an error, provide that information to the user in your message. For example, your error message might say "Sign in to [your app name]" instead of "Please sign in."