Используйте библиотеку приложений Android для автомобилей

Библиотека приложений Android для автомобилей позволяет перенести в автомобиль приложение для навигации , достопримечательностей (POI) , Интернета вещей (IOT) или погоды . Для этого он предоставляет набор шаблонов, разработанных с учетом стандартов по отвлечению внимания водителя, а также учитывает такие детали, как разнообразие факторов экрана автомобиля и способы ввода.

В этом руководстве представлен обзор основных функций и концепций библиотеки, а также описан процесс настройки базового приложения.

Прежде чем начать

  1. Просмотрите страницы «Дизайн для вождения», посвященные библиотеке автомобильных приложений.
  2. Ознакомьтесь с ключевыми терминами и понятиями в следующем разделе.
  3. Ознакомьтесь с пользовательским интерфейсом системы Android Auto и дизайном ОС Android Automotive .
  4. Просмотрите примечания к выпуску .
  5. Просмотрите образцы .

Ключевые термины и понятия

Модели и шаблоны
Пользовательский интерфейс представлен графом объектов модели, которые можно располагать различными способами в зависимости от шаблона, которому они принадлежат. Шаблоны — это подмножество моделей, которые могут выступать в качестве корня в этих графах. Модели включают информацию, которая будет отображаться пользователю в виде текста и изображений, а также атрибуты для настройки аспектов визуального представления такой информации, например цвета текста или размеры изображения. Ведущий преобразует модели в представления, разработанные с учетом стандартов по отвлечению внимания водителя, и учитывает такие детали, как разнообразие факторов экрана автомобиля и способы ввода.
Хозяин
Хост — это серверный компонент, который реализует функциональные возможности, предлагаемые API-интерфейсами библиотеки, поэтому ваше приложение может работать в автомобиле. Обязанности хоста варьируются от обнаружения вашего приложения и управления его жизненным циклом до преобразования ваших моделей в представления и уведомления вашего приложения о взаимодействиях с пользователем. На мобильных устройствах этот хост реализуется Android Auto. В Android Automotive OS этот хост устанавливается как системное приложение.
Ограничения шаблона
Различные шаблоны накладывают ограничения на содержимое своих моделей. Например, шаблоны списков имеют ограничения на количество элементов, которые могут быть представлены пользователю. Шаблоны также имеют ограничения на способ их подключения для формирования потока задачи. Например, приложение может помещать в стек экрана только до пяти шаблонов. Дополнительные сведения см. в разделе Ограничения шаблона .
Screen
Screen — это класс, предоставляемый библиотекой, которую приложения реализуют для управления пользовательским интерфейсом, представленным пользователю. Screen имеет жизненный цикл и предоставляет приложению механизм отправки шаблона для отображения, когда экран виден. Экземпляры Screen также можно помещать и извлекать из стека Screen , что гарантирует соблюдение ограничений потока шаблона .
CarAppService
CarAppService — это абстрактный класс Service , который ваше приложение должно реализовать и экспортировать, чтобы хост мог его обнаружить и управлять им. CarAppService вашего приложения отвечает за проверку того, можно ли доверять хост-соединению с помощью createHostValidator , а затем предоставляет экземпляры Session для каждого соединения с помощью onCreateSession .
Session

Session — это абстрактный класс, который ваше приложение должно реализовать и вернуть с помощью CarAppService.onCreateSession . Он служит точкой входа для отображения информации на экране автомобиля. У него есть жизненный цикл , который информирует о текущем состоянии вашего приложения на экране автомобиля, например, когда ваше приложение видимо или скрыто.

При запуске Session , например при первом запуске приложения, хост запрашивает отображение начального Screen с помощью метода onCreateScreen .

Установите библиотеку автомобильных приложений

Инструкции по добавлению библиотеки в ваше приложение см. на странице выпуска библиотеки Jetpack.

Настройте файлы манифеста вашего приложения

Прежде чем вы сможете создать автомобильное приложение, настройте файлы манифеста вашего приложения следующим образом.

Объявите свой CarAppService

Хост подключается к вашему приложению через реализацию CarAppService . Вы объявляете эту службу в своем манифесте, чтобы позволить хосту обнаружить ваше приложение и подключиться к нему.

Вам также необходимо объявить категорию вашего приложения в элементе <category> фильтра намерений вашего приложения. Значения, разрешенные для этого элемента, см. в списке поддерживаемых категорий приложений .

В следующем фрагменте кода показано, как объявить службу автомобильного приложения для приложения «Точки интереса» в манифесте:

<application>
    ...
   <service
       ...
        android:name=".MyCarAppService"
        android:exported="true">
      <intent-filter>
        <action android:name="androidx.car.app.CarAppService"/>
        <category android:name="androidx.car.app.category.POI"/>
      </intent-filter>
    </service>

    ...
<application>

Поддерживаемые категории приложений

Объявите категорию своего приложения, добавив одно или несколько из следующих значений категории в фильтр намерений при объявлении CarAppService , как описано в предыдущем разделе :

Подробное описание каждой категории и критериев принадлежности приложений к ним см. в разделе «Качество приложений Android для автомобилей».

Укажите имя и значок приложения

Вам необходимо указать имя и значок приложения, которые хост может использовать для представления вашего приложения в пользовательском интерфейсе системы.

Вы можете указать имя и значок приложения, которые будут представлять ваше приложение, используя атрибуты label и icon вашего CarAppService :

...
<service
   android:name=".MyCarAppService"
   android:exported="true"
   android:label="@string/my_app_name"
   android:icon="@drawable/my_app_icon">
   ...
</service>
...

Если метка или значок не объявлены в элементе <service> , хост возвращается к значениям, указанным для элемента <application> .

Установить собственную тему

Чтобы установить собственную тему для вашего автомобильного приложения, добавьте элемент <meta-data> в файл манифеста следующим образом:

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

Затем объявите свой ресурс стиля , чтобы установить следующие атрибуты для вашей пользовательской темы автомобильного приложения:

<resources>
  <style name="MyCarAppTheme">
    <item name="carColorPrimary">@layout/my_primary_car_color</item>
    <item name="carColorPrimaryDark">@layout/my_primary_dark_car_color</item>
    <item name="carColorSecondary">@layout/my_secondary_car_color</item>
    <item name="carColorSecondaryDark">@layout/my_secondary_dark_car_color</item>
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

Уровень API автомобильного приложения

Библиотека автомобильных приложений определяет свои собственные уровни API, чтобы вы могли знать, какие функции библиотеки поддерживаются узлом шаблона на автомобиле. Чтобы получить самый высокий уровень API автомобильного приложения, поддерживаемый хостом, используйте метод getCarAppApiLevel() .

Укажите минимальный уровень API автомобильного приложения, поддерживаемый вашим приложением, в файле AndroidManifest.xml :

<manifest ...>
    <application ...>
        <meta-data
            android:name="androidx.car.app.minCarApiLevel"
            android:value="1"/>
    </application>
</manifest>

Подробную информацию о том, как обеспечить обратную совместимость и объявить минимальный уровень API, необходимый для использования функции, см. в документации к аннотации RequiresCarApi Чтобы определить, какой уровень API необходим для использования определенной функции библиотеки автомобильных приложений, обратитесь к справочной документации по CarAppApiLevels .

Создайте свой CarAppService и сеанс.

Вашему приложению необходимо расширить класс CarAppService и реализовать его метод onCreateSession , который возвращает экземпляр Session , соответствующий текущему соединению с хостом:

Котлин

class HelloWorldService : CarAppService() {
    ...
    override fun onCreateSession(): Session {
        return HelloWorldSession()
    }
    ...
}

Ява

public final class HelloWorldService extends CarAppService {
    ...
    @Override
    @NonNull
    public Session onCreateSession() {
        return new HelloWorldSession();
    }
    ...
}

Экземпляр Session отвечает за возврат экземпляра Screen для использования при первом запуске приложения:

Котлин

class HelloWorldSession : Session() {
    ...
    override fun onCreateScreen(intent: Intent): Screen {
        return HelloWorldScreen(carContext)
    }
    ...
}

Ява

public final class HelloWorldSession extends Session {
    ...
    @Override
    @NonNull
    public Screen onCreateScreen(@NonNull Intent intent) {
        return new HelloWorldScreen(getCarContext());
    }
    ...
}

Чтобы обрабатывать сценарии, в которых вашему автомобильному приложению необходимо запускаться с экрана, который не является домашним или целевым экраном вашего приложения, например, при обработке глубоких ссылок, вы можете предварительно заполнить задний стек экранов с помощью ScreenManager.push перед возвратом из onCreateScreen . Предварительное заполнение позволяет пользователям вернуться к предыдущим экранам с первого экрана, отображаемого вашим приложением.

Создайте стартовый экран

Вы создаете экраны, отображаемые вашим приложением, определяя классы, расширяющие класс Screen , и реализуя его метод onGetTemplate , который возвращает экземпляр Template , представляющий состояние пользовательского интерфейса для отображения на экране автомобиля.

В следующем фрагменте показано, как объявить Screen , который использует шаблон PaneTemplate для отображения простого сообщения «Hello world!» нить:

Котлин

class HelloWorldScreen(carContext: CarContext) : Screen(carContext) {
    override fun onGetTemplate(): Template {
        val row = Row.Builder().setTitle("Hello world!").build()
        val pane = Pane.Builder().addRow(row).build()
        return PaneTemplate.Builder(pane)
            .setHeaderAction(Action.APP_ICON)
            .build()
    }
}

Ява

public class HelloWorldScreen extends Screen {
    @NonNull
    @Override
    public Template onGetTemplate() {
        Row row = new Row.Builder().setTitle("Hello world!").build();
        Pane pane = new Pane.Builder().addRow(row).build();
        return new PaneTemplate.Builder(pane)
            .setHeaderAction(Action.APP_ICON)
            .build();
    }
}

Класс CarContext

Класс CarContext — это подкласс ContextWrapper доступный для ваших экземпляров Session и Screen . Предоставляет доступ к автосервисам, таким как ScreenManager для управления стеком экрана ; AppManager для общих функций, связанных с приложением, таких как доступ к объекту Surface для рисования карт ; и NavigationManager , используемый приложениями пошаговой навигации для передачи метаданных навигации и других событий, связанных с навигацией, с хостом.

Полный список функций библиотеки, доступных для навигационных приложений, см. в разделе Доступ к шаблонам навигации .

CarContext также предлагает другие функции, такие как возможность загрузки доступных для рисования ресурсов с использованием конфигурации с экрана автомобиля, запуск приложения в автомобиле с использованием намерений и сигнализация о том, должно ли ваше приложение отображать свою карту в темной теме .

Реализация экранной навигации

Приложения часто представляют несколько разных экранов, каждый из которых может использовать разные шаблоны, по которым пользователь может перемещаться при взаимодействии с интерфейсом, отображаемым на экране.

Класс ScreenManager предоставляет стек экранов, который можно использовать для отправки экранов, которые могут открываться автоматически, когда пользователь выбирает кнопку «Назад» на экране автомобиля или использует аппаратную кнопку «Назад», доступную в некоторых автомобилях.

В следующем фрагменте показано, как добавить действие «Назад» в шаблон сообщения, а также действие, которое открывает новый экран при выборе пользователем:

Котлин

val template = MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener { screenManager.push(NextScreen(carContext)) }
            .build())
    .build()

Ява

MessageTemplate template = new MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        new Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener(
                () -> getScreenManager().push(new NextScreen(getCarContext())))
            .build())
    .build();

Объект Action.BACK — это стандартное Action , которое автоматически вызывает ScreenManager.pop . Это поведение можно переопределить с помощью экземпляра OnBackPressedDispatcher , доступного из CarContext .

Чтобы обеспечить безопасность использования приложения во время вождения, стек экранов может иметь максимальную глубину в пять экранов. Дополнительную информацию см. в разделе «Ограничения шаблонов» .

Обновить содержимое шаблона

Ваше приложение может запросить признание содержимого Screen недействительным, вызвав метод Screen.invalidate . Впоследствии хост вызывает метод Screen.onGetTemplate вашего приложения, чтобы получить шаблон с новым содержимым.

При обновлении Screen важно понимать конкретное содержимое шаблона, которое можно обновить, чтобы хост не учитывал новый шаблон в квоте шаблона. Дополнительную информацию см. в разделе «Ограничения шаблона» .

Мы рекомендуем вам структурировать свои экраны так, чтобы между Screen и типом шаблона, который он возвращает через реализацию onGetTemplate , было однозначное соответствие.

Рисовать карты

Приложения навигации, достопримечательностей (POI) и погоды, использующие следующие шаблоны, могут рисовать карты, обращаясь к Surface .

Чтобы использовать следующие шаблоны, ваше приложение должно иметь одно из соответствующих разрешений, объявленных в элементе <uses-permission> в файле AndroidManifest.xml .

Шаблон Разрешение шаблона Руководство по категориям
NavigationTemplate androidx.car.app.NAVIGATION_TEMPLATES Навигация
MapWithContentTemplate androidx.car.app.NAVIGATION_TEMPLATES ИЛИ
androidx.car.app.MAP_TEMPLATES
Навигация , POI , Погода
MapTemplate ( устарело ) androidx.car.app.NAVIGATION_TEMPLATES Навигация
PlaceListNavigationTemplate ( устарело ) androidx.car.app.NAVIGATION_TEMPLATES Навигация
RoutePreviewNavigationTemplate ( устарело ) androidx.car.app.NAVIGATION_TEMPLATES Навигация

Объявить разрешение на поверхность

В дополнение к разрешению, необходимому для шаблона, который использует ваше приложение, ваше приложение должно объявить разрешение androidx.car.app.ACCESS_SURFACE в своем файле AndroidManifest.xml , чтобы получить доступ к поверхности:

<manifest ...>
  ...
  <uses-permission android:name="androidx.car.app.ACCESS_SURFACE" />
  ...
</manifest>

Доступ к поверхности

Чтобы получить доступ к Surface , предоставляемому узлом, необходимо реализовать SurfaceCallback и предоставить эту реализацию автомобильной службе AppManager . Текущая Surface передается в ваш SurfaceCallback в параметре SurfaceContainer обратных вызовов onSurfaceAvailable() и onSurfaceDestroyed() .

Котлин

carContext.getCarService(AppManager::class.java).setSurfaceCallback(surfaceCallback)

Ява

carContext.getCarService(AppManager.class).setSurfaceCallback(surfaceCallback);

Понять видимую область поверхности

Организатор может рисовать элементы пользовательского интерфейса для шаблонов поверх карты. Хост сообщает область поверхности, которая гарантированно будет беспрепятственной и полностью видимой пользователю, вызывая метод SurfaceCallback.onVisibleAreaChanged . Кроме того, чтобы минимизировать количество изменений, хост вызывает метод SurfaceCallback.onStableAreaChanged с наименьшим прямоугольником, который всегда виден на основе текущего шаблона.

Например, когда навигационное приложение использует NavigationTemplate с полосой действий вверху, полоса действий может скрываться, когда пользователь какое-то время не взаимодействовал с экраном, чтобы освободить больше места для карты. В этом случае происходит обратный вызов onStableAreaChanged и onVisibleAreaChanged с тем же прямоугольником. Когда полоса действий скрыта, вызывается только onVisibleAreaChanged с большей областью. Если пользователь взаимодействует с экраном, то снова вызывается только onVisibleAreaChanged с первым прямоугольником.

Поддержка темной темы

Приложения должны перерисовать свою карту на экземпляре Surface с использованием соответствующих темных цветов, когда хост определяет, что условия требуют этого, как описано в разделе «Качество приложений Android для автомобилей» .

Чтобы решить, рисовать ли темную карту, вы можете использовать метод CarContext.isDarkMode . Всякий раз, когда статус темной темы меняется, вы получаете вызов Session.onCarConfigurationChanged .

Позвольте пользователям взаимодействовать с вашей картой

При использовании следующих шаблонов вы можете добавить поддержку взаимодействия пользователей с нарисованными вами картами, например позволить им видеть различные части карты путем масштабирования и панорамирования.

Шаблон Интерактивность поддерживается начиная с уровня API автомобильного приложения.
NavigationTemplate 2
PlaceListNavigationTemplate ( устарело ) 4
RoutePreviewNavigationTemplate ( устарело ) 4
MapTemplate ( устарело ) 5 (знакомство с шаблоном)
MapWithContentTemplate 7 (знакомство с шаблоном)

Реализация интерактивных обратных вызовов

Интерфейс SurfaceCallback имеет несколько методов обратного вызова, которые можно реализовать, чтобы добавить интерактивности к картам, созданным с помощью шаблонов из предыдущего раздела:

Взаимодействие Метод SurfaceCallback Поддерживается начиная с уровня Car App API.
Кран onClick 5
Сведите пальцы, чтобы увеличить onScale 2
Перетаскивание одним касанием onScroll 2
Бросок в одно касание onFling 2
Дважды коснитесь onScale (с коэффициентом масштабирования, определяемым хостом шаблона) 2
Вращающееся перемещение в режиме панорамирования onScroll (с коэффициентом расстояния, определяемым хостом шаблона) 2

Добавить полосу действий карты

Эти шаблоны могут иметь полосу действий карты для действий, связанных с картой, таких как увеличение и уменьшение масштаба, повторное центрирование, отображение компаса и других действий, которые вы выбираете для отображения. Полоса действий карты может содержать до четырех кнопок со значками, которые можно обновлять, не влияя на глубину задачи. Он скрывается в режиме ожидания и снова появляется в активном состоянии.

Чтобы получать обратные вызовы интерактивности карты, необходимо добавить кнопку Action.PAN в полосу действий карты. Когда пользователь нажимает кнопку панорамирования, хост переходит в режим панорамирования, как описано в следующем разделе.

Если в вашем приложении отсутствует кнопка Action.PAN на полосе действий карты, оно не получает пользовательский ввод от методов SurfaceCallback , и узел выходит из любого ранее активированного режима панорамирования.

На сенсорном экране кнопка панорамирования не отображается.

Понимание режима панорамирования

В режиме панорамирования хост шаблона преобразует ввод пользователя с устройств ввода без сенсорного ввода, таких как поворотные контроллеры и сенсорные панели, в соответствующие методы SurfaceCallback . Ответьте на действие пользователя, чтобы войти в режим панорамирования или выйти из него, с помощью метода setPanModeListener в NavigationTemplate.Builder . Хост может скрыть другие компоненты пользовательского интерфейса в шаблоне, пока пользователь находится в режиме панорамирования.

Взаимодействуйте с пользователем

Ваше приложение может взаимодействовать с пользователем, используя шаблоны, аналогичные мобильному приложению.

Обработка ввода пользователя

Ваше приложение может реагировать на ввод пользователя, передавая соответствующие прослушиватели моделям, которые их поддерживают. В следующем фрагменте показано, как создать модель Action , которая устанавливает OnClickListener , который выполняет обратный вызов метода, определенного кодом вашего приложения:

Котлин

val action = Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(::onClickNavigate)
    .build()

Ява

Action action = new Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(this::onClickNavigate)
    .build();

Затем метод onClickNavigate может запустить автомобильное навигационное приложение по умолчанию с помощью метода CarContext.startCarApp :

Котлин

private fun onClickNavigate() {
    val intent = Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address))
    carContext.startCarApp(intent)
}

Ява

private void onClickNavigate() {
    Intent intent = new Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address));
    getCarContext().startCarApp(intent);
}

Дополнительные сведения о том, как запускать приложения, включая формат намерения ACTION_NAVIGATE , см. в разделе «Запуск автомобильного приложения с намерением» .

Некоторые действия, например требующие от пользователя продолжить взаимодействие на мобильных устройствах, разрешены только тогда, когда автомобиль припаркован. Вы можете использовать ParkedOnlyOnClickListener для реализации этих действий. Если автомобиль не припаркован, хост отображает пользователю сообщение о том, что действие в этом случае не разрешено. Если автомобиль припаркован, код выполняется нормально. В следующем фрагменте показано, как использовать ParkedOnlyOnClickListener для открытия экрана настроек на мобильном устройстве:

Котлин

val row = Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(::openSettingsOnPhone))
    .build()

Ява

Row row = new Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(this::openSettingsOnPhone))
    .build();

Отображать уведомления

Уведомления, отправленные на мобильное устройство, отображаются на экране автомобиля только в том случае, если они расширены с помощью CarAppExtender . Некоторые атрибуты уведомлений, такие как заголовок контента, текст, значок и действия, можно установить в CarAppExtender , переопределяя атрибуты уведомления, когда они появляются на экране автомобиля.

В следующем фрагменте показано, как отправить на экран автомобиля уведомление, на котором отображается заголовок, отличный от того, который отображается на мобильном устройстве:

Котлин

val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build()

Ява

Notification notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        new CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build();

Уведомления могут влиять на следующие части пользовательского интерфейса:

  • Пользователю может быть отображено хед-ап-уведомление (HUN).
  • Можно добавить запись в центр уведомлений, при необходимости со значком, видимым на направляющей.
  • Для навигационных приложений уведомление может отображаться в виджете железной дороги, как описано в разделе «Уведомления о поворотах» .

Вы можете выбрать, как настроить уведомления вашего приложения, чтобы они влияли на каждый из этих элементов пользовательского интерфейса, используя приоритет уведомления, как описано в документации CarAppExtender .

Если NotificationCompat.Builder.setOnlyAlertOnce вызывается со значением true , уведомление с высоким приоритетом отображается как HUN только один раз.

Дополнительную информацию о том, как создавать уведомления для автомобильного приложения, см. в руководстве Google Design for Driving об уведомлениях .

Показать тосты

Ваше приложение может отображать всплывающее уведомление с помощью CarToast , как показано в этом фрагменте:

Котлин

CarToast.makeText(carContext, "Hello!", CarToast.LENGTH_SHORT).show()

Ява

CarToast.makeText(getCarContext(), "Hello!", CarToast.LENGTH_SHORT).show();

Запросить разрешения

Если вашему приложению требуется доступ к ограниченным данным или действиям (например, к местоположению), применяются стандартные правила разрешений Android . Чтобы запросить разрешение, вы можете использовать метод CarContext.requestPermissions() .

Преимущество использования CarContext.requestPermissions() по сравнению со стандартными API-интерфейсами Android заключается в том, что вам не нужно запускать собственное Activity для создания диалогового окна разрешений. Более того, вы можете использовать один и тот же код как в Android Auto, так и в Android Automotive OS, вместо того, чтобы создавать потоки, зависящие от платформы.

Оформление диалогового окна разрешений в Android Auto

В Android Auto на телефоне появится диалоговое окно разрешений для пользователя. По умолчанию за диалогом не будет фона. Чтобы установить собственный фон, объявите тему автомобильного приложения в файле AndroidManifest.xml и установите атрибут carPermissionActivityLayout для темы автомобильного приложения.

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

Затем установите атрибут carPermissionActivityLayout для темы автомобильного приложения:

<resources>
  <style name="MyCarAppTheme">
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

Запустите автомобильное приложение с намерением

Вы можете вызвать метод CarContext.startCarApp , чтобы выполнить одно из следующих действий:

В следующем примере показано, как создать уведомление с действием, которое открывает ваше приложение с экраном, отображающим сведения о резервировании парковки. Вы расширяете экземпляр уведомления с помощью намерения содержимого, которое содержит PendingIntent , обертывающее явное намерение для действия вашего приложения:

Котлин

val notification = notificationBuilder
    ...
    .extend(
        CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(ComponentName(context, MyNotificationReceiver::class.java)),
                    0))
            .build())

Ява

Notification notification = notificationBuilder
    ...
    .extend(
        new CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    new Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(new ComponentName(context, MyNotificationReceiver.class)),
                    0))
            .build());

Ваше приложение также должно объявить BroadcastReceiver , который вызывается для обработки намерения, когда пользователь выбирает действие в интерфейсе уведомлений и вызывает CarContext.startCarApp с намерением, включая URI данных:

Котлин

class MyNotificationReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val intentAction = intent.action
        if (ACTION_VIEW_PARKING_RESERVATION == intentAction) {
            CarContext.startCarApp(
                intent,
                Intent(Intent.ACTION_VIEW)
                    .setComponent(ComponentName(context, MyCarAppService::class.java))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)))
        }
    }
}

Ява

public class MyNotificationReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String intentAction = intent.getAction();
        if (ACTION_VIEW_PARKING_RESERVATION.equals(intentAction)) {
            CarContext.startCarApp(
                intent,
                new Intent(Intent.ACTION_VIEW)
                    .setComponent(new ComponentName(context, MyCarAppService.class))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)));
        }
    }
}

Наконец, метод Session.onNewIntent в вашем приложении обрабатывает это намерение, помещая экран резервирования парковки в стек, если он еще не находится наверху:

Котлин

override fun onNewIntent(intent: Intent) {
    val screenManager = carContext.getCarService(ScreenManager::class.java)
    val uri = intent.data
    if (uri != null
        && MY_URI_SCHEME == uri.scheme
        && MY_URI_HOST == uri.schemeSpecificPart
        && ACTION_VIEW_PARKING_RESERVATION == uri.fragment
    ) {
        val top = screenManager.top
        if (top !is ParkingReservationScreen) {
            screenManager.push(ParkingReservationScreen(carContext))
        }
    }
}

Ява

@Override
public void onNewIntent(@NonNull Intent intent) {
    ScreenManager screenManager = getCarContext().getCarService(ScreenManager.class);
    Uri uri = intent.getData();
    if (uri != null
        && MY_URI_SCHEME.equals(uri.getScheme())
        && MY_URI_HOST.equals(uri.getSchemeSpecificPart())
        && ACTION_VIEW_PARKING_RESERVATION.equals(uri.getFragment())
    ) {
        Screen top = screenManager.getTop();
        if (!(top instanceof ParkingReservationScreen)) {
            screenManager.push(new ParkingReservationScreen(getCarContext()));
        }
    }
}

Дополнительную информацию о том, как обрабатывать уведомления автомобильного приложения, см. в разделе «Отображение уведомлений» .

Ограничения шаблона

Хост ограничивает количество шаблонов, отображаемых для данной задачи, максимум пятью, из которых последний шаблон должен быть одного из следующих типов:

Обратите внимание, что это ограничение применяется к количеству шаблонов, а не к количеству экземпляров Screen в стеке. Например, если приложение отправляет два шаблона, находясь на экране А, а затем отправляет экран Б, теперь оно может отправить еще три шаблона. В качестве альтернативы, если каждый экран структурирован для отправки одного шаблона, приложение может поместить пять экземпляров экрана в стек ScreenManager .

У этих ограничений есть особые случаи: обновление шаблона, а также операции возврата и сброса.

Шаблон обновляется

Определенные обновления контента не учитываются в лимите шаблона. Как правило, если приложение отправляет новый шаблон того же типа и содержит тот же основной контент, что и предыдущий шаблон, новый шаблон не учитывается в квоте. Например, обновление состояния переключения строки в ListTemplate не учитывается в квоте. См. документацию по отдельным шаблонам, чтобы узнать больше о том, какие типы обновлений контента можно считать обновлением.

Обратные операции

Чтобы включить подпотоки внутри задачи, хост определяет, когда приложение извлекает Screen из стека ScreenManager , и обновляет оставшуюся квоту на основе количества шаблонов, по которым приложение движется назад.

Например, если приложение отправляет два шаблона, находясь на экране A, затем отправляет экран B и отправляет еще два шаблона, у приложения остается одна квота. Если затем приложение возвращается на экран A, хост сбрасывает квоту до трех, поскольку приложение перешло на два шаблона назад.

Обратите внимание, что при возвращении на экран приложение должно отправить шаблон того же типа, что и последний отправленный с этого экрана. Отправка любого другого типа шаблона вызывает ошибку. Однако, пока тип остается неизменным во время обратной операции, приложение может свободно изменять содержимое шаблона, не затрагивая квоту.

Сброс операций

Некоторые шаблоны имеют специальную семантику, обозначающую завершение задачи. Например, NavigationTemplate — это представление, которое, как ожидается, останется на экране и будет обновляться новыми пошаговыми инструкциями для использования пользователем. Когда он достигает одного из этих шаблонов, хост сбрасывает квоту шаблона, рассматривая этот шаблон как первый шаг новой задачи. Это позволит приложению начать новую задачу. См. документацию по отдельным шаблонам, чтобы узнать, какие из них вызывают сброс на хосте.

Если хост получает намерение запустить приложение из действия уведомления или из средства запуска, квота также сбрасывается. Этот механизм позволяет приложению начать новый поток задач из уведомлений, и он действует, даже если приложение уже привязано и находится на переднем плане.

Дополнительную информацию о том, как отображать уведомления вашего приложения на экране автомобиля, см. в разделе «Отображение уведомлений» . Информацию о том, как запустить приложение с помощью действия уведомления, см. в разделе «Запуск автомобильного приложения с намерением» .

API подключения

Вы можете определить, работает ли ваше приложение на ОС Android Auto или Android Automotive, используя API CarConnection для получения информации о подключении во время выполнения.

Например, в Session вашего автомобильного приложения инициализируйте CarConnection и подпишитесь на обновления LiveData :

Котлин

CarConnection(carContext).type.observe(this, ::onConnectionStateUpdated)

Ява

new CarConnection(getCarContext()).getType().observe(this, this::onConnectionStateUpdated);

Затем в наблюдателе вы можете реагировать на изменения состояния соединения:

Котлин

fun onConnectionStateUpdated(connectionState: Int) {
  val message = when(connectionState) {
    CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not connected to a head unit"
    CarConnection.CONNECTION_TYPE_NATIVE -> "Connected to Android Automotive OS"
    CarConnection.CONNECTION_TYPE_PROJECTION -> "Connected to Android Auto"
    else -> "Unknown car connection type"
  }
  CarToast.makeText(carContext, message, CarToast.LENGTH_SHORT).show()
}

Ява

private void onConnectionStateUpdated(int connectionState) {
  String message;
  switch(connectionState) {
    case CarConnection.CONNECTION_TYPE_NOT_CONNECTED:
      message = "Not connected to a head unit";
      break;
    case CarConnection.CONNECTION_TYPE_NATIVE:
      message = "Connected to Android Automotive OS";
      break;
    case CarConnection.CONNECTION_TYPE_PROJECTION:
      message = "Connected to Android Auto";
      break;
    default:
      message = "Unknown car connection type";
      break;
  }
  CarToast.makeText(getCarContext(), message, CarToast.LENGTH_SHORT).show();
}

API ограничений

Разные автомобили могут отображать пользователю разное количество экземпляров Item одновременно. Используйте ConstraintManager , чтобы проверить ограничение содержимого во время выполнения и установить соответствующее количество элементов в ваших шаблонах.

Начните с получения ConstraintManager из CarContext :

Котлин

val manager = carContext.getCarService(ConstraintManager::class.java)

Ява

ConstraintManager manager = getCarContext().getCarService(ConstraintManager.class);

Затем вы можете запросить полученный объект ConstraintManager для получения соответствующего ограничения содержимого. Например, чтобы получить количество элементов, которые могут отображаться в сетке, вызовите getContentLimit с CONTENT_LIMIT_TYPE_GRID :

Котлин

val gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID)

Ява

int gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID);

Добавьте процесс входа в систему

Если ваше приложение предлагает пользователям возможность входа в систему, вы можете использовать такие шаблоны, как SignInTemplate и LongMessageTemplate с API Car App уровня 2 и выше, чтобы обрабатывать вход в ваше приложение на головном устройстве автомобиля.

Чтобы создать SignInTemplate , определите SignInMethod . Библиотека автомобильных приложений в настоящее время поддерживает следующие способы входа:

  • InputSignInMethod для входа в систему по имени пользователя и паролю.
  • PinSignInMethod для входа в систему с помощью PIN-кода, при котором пользователь связывает свою учетную запись со своего телефона с помощью PIN-кода, отображаемого на головном устройстве.
  • ProviderSignInMethod для входа в систему поставщика, например Google Sign-In и One Tap .
  • QRCodeSignInMethod для входа в систему с помощью QR-кода, при котором пользователь сканирует QR-код для завершения входа на своем телефоне. Это доступно при уровне Car API 4 и выше.

Например, чтобы реализовать шаблон, собирающий пароль пользователя, начните с создания InputCallback для обработки и проверки ввода пользователя:

Котлин

val callback = object : InputCallback {
    override fun onInputSubmitted(text: String) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    override fun onInputTextChanged(text: String) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
}

Ява

InputCallback callback = new InputCallback() {
    @Override
    public void onInputSubmitted(@NonNull String text) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    @Override
    public void onInputTextChanged(@NonNull String text) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
};

Для InputSignInMethod Builder требуется InputCallback .

Котлин

val passwordInput = InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build()

Ява

InputSignInMethod passwordInput = new InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build();

Наконец, используйте новый InputSignInMethod для создания SignInTemplate .

Котлин

SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build()

Ява

new SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build();

Используйте Менеджер учетных записей

Приложения Android Automotive OS, имеющие аутентификацию, должны использовать AccountManager по следующим причинам:

  • Улучшенный пользовательский интерфейс и простота управления учетными записями . Пользователи могут легко управлять всеми своими учетными записями из меню учетных записей в настройках системы, включая вход и выход.
  • «Гостевой» опыт : поскольку автомобили являются общими устройствами, OEM-производители могут включить гостевой интерфейс в автомобиле, где учетные записи не могут быть добавлены.

Добавить варианты текстовой строки

Различные размеры автомобильных экранов могут отображать разное количество текста. Используя Car App API уровня 2 и выше, вы можете указать несколько вариантов текстовой строки, чтобы она лучше всего вписывалась в экран. Чтобы узнать, где принимаются варианты текста, найдите шаблоны и компоненты, которые принимают CarText .

Вы можете добавить варианты текстовой строки в CarText с помощью метода CarText.Builder.addVariant() :

Котлин

val itemTitle = CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build()

Ява

CarText itemTitle = new CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build();

Затем вы можете использовать этот CarText , например, в качестве основного текста GridItem .

Котлин

GridItem.Builder()
    .addTitle(itemTitle)
    ...
    .build()

Ява

new GridItem.Builder()
    .addTitle(itemTitle)
    ...
    build();

Добавляйте строки в порядке от наиболее предпочтительного к наименее предпочтительному, например от самого длинного к самому короткому. Хост выбирает строку подходящей длины в зависимости от количества доступного места на экране автомобиля.

Добавить встроенные CarIcons для строк

Вы можете добавлять значки вместе с текстом, чтобы сделать ваше приложение более привлекательным, используя CarIconSpan . Дополнительную информацию о создании этих диапазонов см. в документации по CarIconSpan.create . См. раздел «Стилизация текста Spantastic с помощью интервалов», чтобы получить обзор того, как работает стилизация текста с помощью интервалов.

Котлин

  
val rating = SpannableString("Rating: 4.5 stars")
rating.setSpan(
    CarIconSpan.create(
        // Create a CarIcon with an image of four and a half stars
        CarIcon.Builder(...).build(),
        // Align the CarIcon to the baseline of the text
        CarIconSpan.ALIGN_BASELINE
    ),
    // The start index of the span (index of the character '4')
    8,
    // The end index of the span (index of the last 's' in "stars")
    16,
    Spanned.SPAN_INCLUSIVE_INCLUSIVE
)

val row = Row.Builder()
    ...
    .addText(rating)
    .build()
  
  

Ява

  
SpannableString rating = new SpannableString("Rating: 4.5 stars");
rating.setSpan(
        CarIconSpan.create(
                // Create a CarIcon with an image of four and a half stars
                new CarIcon.Builder(...).build(),
                // Align the CarIcon to the baseline of the text
                CarIconSpan.ALIGN_BASELINE
        ),
        // The start index of the span (index of the character '4')
        8,
        // The end index of the span (index of the last 's' in "stars")
        16,
        Spanned.SPAN_INCLUSIVE_INCLUSIVE
);
Row row = new Row.Builder()
        ...
        .addText(rating)
        .build();
  
  

API автомобильного оборудования

Начиная с уровня 3 Car App API, в библиотеке автомобильных приложений есть API, которые можно использовать для доступа к свойствам и датчикам автомобиля.

Требования

Чтобы использовать API с Android Auto, начните с добавления зависимости androidx.car.app:app-projected в файл build.gradle для вашего модуля Android Auto. Для Android Automotive OS добавьте зависимость androidx.car.app:app-automotive в файл build.gradle вашего модуля Android Automotive OS.

Кроме того, в файле AndroidManifest.xml вам необходимо объявить соответствующие разрешения, необходимые для запроса данных автомобиля, которые вы хотите использовать. Обратите внимание, что эти разрешения также должны быть предоставлены вам пользователем. Вы можете использовать один и тот же код как в Android Auto, так и в Android Automotive OS, вместо того, чтобы создавать потоки, зависящие от платформы. Однако необходимые разрешения разные.

Информация об автомобиле

В этой таблице описаны свойства, предоставляемые API-интерфейсами CarInfo , и разрешения, которые необходимо запросить для их использования:

Методы Характеристики Автоматические разрешения Android Разрешения автомобильной ОС Android Поддерживается начиная с уровня Car App API.
fetchModel Марка, модель, год android.car.permission.CAR_INFO 3
fetchEnergyProfile Типы разъемов электромобилей, типы топлива com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_INFO 3
fetchExteriorDimensions

Эти данные доступны только на некоторых автомобилях с ОС Android Automotive под управлением API 30 или выше.

Внешние размеры Н/Д android.car.permission.CAR_INFO 7
addTollListener
removeTollListener
Состояние платной карты, тип платной карты 3
addEnergyLevelListener
removeEnergyLevelListener
Уровень заряда батареи, уровень топлива, низкий уровень топлива, оставшийся запас хода com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_ENERGY ,
android.car.permission.CAR_ENERGY_PORTS ,
android.car.permission.READ_CAR_DISPLAY_UNITS
3
addSpeedListener
removeSpeedListener
Необработанная скорость, скорость отображения (отображается на приборном дисплее автомобиля) com.google.android.gms.permission.CAR_SPEED android.car.permission.CAR_SPEED ,
android.car.permission.READ_CAR_DISPLAY_UNITS
3
addMileageListener
removeMileageListener

Внимание: метод getOdometerMeters класса Mileage имеет неточное имя и возвращает километры, а не метры.

Расстояние на одометре com.google.android.gms.permission.CAR_MILEAGE Эти данные недоступны в ОС Android Automotive для приложений, установленных из Play Store. 3

Например, чтобы получить оставшийся диапазон, создайте экземпляр объекта CarInfo , затем создайте и зарегистрируйте OnCarDataAvailableListener :

Котлин

val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo

val listener = OnCarDataAvailableListener<EnergyLevel> { data ->
    if (data.rangeRemainingMeters.status == CarValue.STATUS_SUCCESS) {
      val rangeRemaining = data.rangeRemainingMeters.value
    } else {
      // Handle error
    }
  }

carInfo.addEnergyLevelListener(carContext.mainExecutor, listener)

// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener)

Ява

CarInfo carInfo = getCarContext().getCarService(CarHardwareManager.class).getCarInfo();

OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
  if(data.getRangeRemainingMeters().getStatus() == CarValue.STATUS_SUCCESS) {
    float rangeRemaining = data.getRangeRemainingMeters().getValue();
  } else {
    // Handle error
  }
};

carInfo.addEnergyLevelListener(getCarContext().getMainExecutor(), listener);

// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener);

Не думайте, что данные с автомобиля доступны всегда. Если вы получили сообщение об ошибке, проверьте статус запрошенного вами значения, чтобы лучше понять, почему запрошенные данные не удалось получить. Полное определение класса CarInfo можно найти в справочной документации .

Автомобильные датчики

Класс CarSensors предоставляет вам доступ к акселерометру автомобиля, гироскопа, компаса и данных о местоположении. Доступность этих значений может зависеть от OEM. Формат для данных от акселерометра, гироскопа и компаса, такой же, как вы получите от API SensorManager . Например, чтобы проверить заголовок транспортного средства:

Котлин

val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors

val listener = OnCarDataAvailableListener<Compass> { data ->
    if (data.orientations.status == CarValue.STATUS_SUCCESS) {
      val orientation = data.orientations.value
    } else {
      // Data not available, handle error
    }
  }

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, carContext.mainExecutor, listener)

// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener)

Ява

CarSensors carSensors = getCarContext().getCarService(CarHardwareManager.class).getCarSensors();

OnCarDataAvailableListener<Compass> listener = (data) -> {
  if (data.getOrientations().getStatus() == CarValue.STATUS_SUCCESS) {
    List<Float> orientations = data.getOrientations().getValue();
  } else {
    // Data not available, handle error
  }
};

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, getCarContext().getMainExecutor(),
    listener);

// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener);

Чтобы получить доступ к данным местоположения от автомобиля, вам также необходимо объявить и запросить разрешение android.permission.ACCESS_FINE_LOCATION .

Тестирование

Для моделирования данных датчика при тестировании на Android Auto см. В разделе «Датчики и конфигурацию датчиков» направляющего подразделения настольного настольного блока. Для моделирования данных датчика при тестировании на Android Automotive OS см. В разделе «Эмулятное оборудование» в Руководстве по эмулятору Android Automotive OS.

Жизненные циклы Carappservice, сеанса и экрана

Классы Session и Screen реализуют интерфейс LifecycleOwner . Когда пользователь взаимодействует с приложением, вызывает обратные вызовы на жизненный цикл вашего Session и Screen , как описано на следующих диаграммах.

Жизненные циклы караппервика и сеанс

Рисунок 1 . Жизненный цикл Session .

Для получения полной информации см. Документацию для метода Session.getLifecycle .

Жизненный цикл экрана

Рисунок 2 . Жизненный цикл Screen .

Для получения полной информации см. Документацию для метода Screen.getLifecycle .

Запись из автомобильного микрофона

Используя CarAppService вашего приложения и API CarAudioRecord , вы можете предоставить своему приложению доступ к автомобильному микрофону пользователя. Пользователи должны дать вашему приложению разрешение на доступ к автомобильному микрофону. Ваше приложение может записывать и обработать ввод пользователя в вашем приложении.

Разрешение на запись

Перед записи какого -либо аудио вы должны сначала объявить разрешение на запись в вашем AndroidManifest.xml и попросить пользователя его предоставили.

<manifest ...>
   ...
   <uses-permission android:name="android.permission.RECORD_AUDIO" />
   ...
</manifest>

Вам нужно запросить разрешение на запись во время выполнения. См. Раздел «Разрешения на запрос» для получения подробной информации о том, как запросить разрешение в приложении вашего автомобиля.

Запишите аудио

После того, как пользователь дает разрешение на запись, вы можете записать аудио и обработать запись.

Котлин

val carAudioRecord = CarAudioRecord.create(carContext)
        carAudioRecord.startRecording()

        val data = ByteArray(CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE)
        while(carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording()
 

Ява

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        carAudioRecord.startRecording();

        byte[] data = new byte[CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE];
        while (carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording();
 

Аудио Фокус

При записи из автомобильного микрофона сначала приобретайте аудио фокусировку , чтобы гарантировать, что любые текущие носители будут остановлены. Если вы потеряете аудио фокусировку, прекратите запись.

Вот пример того, как приобрести аудио фокусировку:

Котлин

 
val carAudioRecord = CarAudioRecord.create(carContext)
        
        // Take audio focus so that user's media is not recorded
        val audioAttributes = AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
            // Use the most appropriate usage type for your use case
            .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
            .build()
        
        val audioFocusRequest =
            AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                .setAudioAttributes(audioAttributes)
                .setOnAudioFocusChangeListener { state: Int ->
                    if (state == AudioManager.AUDIOFOCUS_LOSS) {
                        // Stop recording if audio focus is lost
                        carAudioRecord.stopRecording()
                    }
                }
                .build()
        
        if (carContext.getSystemService(AudioManager::class.java)
                .requestAudioFocus(audioFocusRequest)
            != AudioManager.AUDIOFOCUS_REQUEST_GRANTED
        ) {
            // Don't record if the focus isn't granted
            return
        }
        
        carAudioRecord.startRecording()
        // Process the audio and abandon the AudioFocusRequest when done

Ява

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        // Take audio focus so that user's media is not recorded
        AudioAttributes audioAttributes =
                new AudioAttributes.Builder()
                        .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                        // Use the most appropriate usage type for your use case
                        .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
                        .build();

        AudioFocusRequest audioFocusRequest =
                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                        .setAudioAttributes(audioAttributes)
                        .setOnAudioFocusChangeListener(state -> {
                            if (state == AudioManager.AUDIOFOCUS_LOSS) {
                                // Stop recording if audio focus is lost
                                carAudioRecord.stopRecording();
                            }
                        })
                        .build();

        if (getCarContext().getSystemService(AudioManager.class).requestAudioFocus(audioFocusRequest)
                != AUDIOFOCUS_REQUEST_GRANTED) {
            // Don't record if the focus isn't granted
            return;
        }

        carAudioRecord.startRecording();
        // Process the audio and abandon the AudioFocusRequest when done
 

Тестирование библиотеки

Библиотека тестирования Android для автомобилей предоставляет вспомогательные классы, которые вы можете использовать для проверки поведения вашего приложения в тестовой среде. Например, SessionController позволяет имитировать соединение с хостом и проверить, что правильный Screen и Template создаются и возвращаются.

Обратитесь к образцам для примеров использования.

Сообщите о Android для Cars App.

Если вы найдете проблему с библиотекой, сообщите о ней, используя The Google Tracker . Обязательно заполните всю запрошенную информацию в шаблоне выпуска.

Создать новую проблему

Перед подачей новой проблемы, пожалуйста, проверьте, указано ли он в записях библиотеки или сообщена в списке проблем. Вы можете подписаться и проголосовать за проблемы, щелкнув Star за проблему в трекере. Для получения дополнительной информации см. Подписку на проблему .

,

Библиотека приложений для Android for Cars позволяет вам принести навигацию , точку интереса (POI) , Интернет вещей (IoT) или погодное приложение в машину. Это делает это, предоставляя набор шаблонов, предназначенных для соответствия стандартам отвлечения водителя и заботы о таких деталях, как разнообразие факторов экрана автомобиля и методы ввода.

В этом руководстве представлен обзор ключевых функций и концепций библиотеки и проведет вас через процесс настройки базового приложения.

Прежде чем начать

  1. Просмотрите дизайн для вождения страниц, охватывающих библиотеку приложений CAR
  2. Просмотрите ключевые термины и понятия в следующем разделе.
  3. Познакомьтесь с пользовательским интерфейсом Android Auto System и Android Automotive OS .
  4. Просмотрите заметки о выпуске .
  5. Просмотрите образцы .

Ключевые термины и концепции

Модели и шаблоны
Пользовательский интерфейс представлен графом объектов модели, который может быть расположен вместе по -разному, как разрешено шаблоном, к которому они принадлежат. Шаблоны представляют собой подмножество моделей, которые могут действовать как корень в этих графиках. Модели включают информацию, которая будет отображаться пользователю в форме текста и изображений, а также атрибуты для настройки аспектов визуального появления такой информации - например, цвета текста или размеры изображений. Хост преобразует модели в представления, которые предназначены для соответствия стандартам отвлечения водителя, и заботится о деталях, таких как разнообразие факторов экрана автомобиля и методы ввода.
Хозяин
Хост является бэкэнд -компонентом, который реализует функциональность, предлагаемую API библиотеки, чтобы ваше приложение могло работать в автомобиле. Обязанности хоста варьируются от обнаружения вашего приложения и управления его жизненным циклом до преобразования ваших моделей в представления и уведомления вашего приложения о взаимодействиях с пользователями. На мобильных устройствах этот хост реализован Android Auto. На Android Automotive OS этот хост устанавливается в качестве системного приложения.
Ограничения шаблона
Различные шаблоны обеспечивают ограничения в содержании своих моделей. Например, шаблоны списков имеют ограничения на количество элементов, которые могут быть представлены пользователю. Шаблоны также имеют ограничения в том, как они могут быть подключены для формирования потока задачи. Например, приложение может подтолкнуть только к пять шаблонов в стек экрана. Смотрите ограничения шаблонов для более подробной информации.
Screen
Screen - это класс, предоставляемый библиотекой, который реализуют приложения для управления пользовательским интерфейсом, представленным пользователю. Screen имеет жизненный цикл и обеспечивает механизм для приложения для отправки шаблона для отображения при видении экрана. Экционы Screen также могут быть выдвинуты и выскочить в стек Screen , который гарантирует, что они придерживаются ограничений по потоку шаблонов .
CarAppService
CarAppService - это абстрактный класс Service , который ваше приложение должно реализовать и экспортировать, чтобы обнаружить и управлять хостом. CarAppService вашего приложения отвечает за подтверждение того, что подключению хоста может быть доверяет с использованием createHostValidator и впоследствии предоставление экземпляров Session для каждого соединения с использованием onCreateSession .
Session

Session - это абстрактный класс, который ваше приложение должно реализовать и возвращать с помощью CarAppService.onCreateSession . Он служит точкой входа для отображения информации на экране автомобиля. Он имеет жизненный цикл , который информирует текущее состояние вашего приложения на экране автомобиля, например, когда ваше приложение видно или скрыто.

Когда запускается Session , например, когда приложение впервые запускается, хост запрашивает начальный Screen отображать с помощью метода onCreateScreen .

Установите библиотеку приложений CAR

Смотрите страницу выпуска библиотеки JetPack для инструкций о том, как добавить библиотеку в ваше приложение.

Настройте манифестные файлы вашего приложения

Прежде чем вы сможете создать свое автомобильное приложение, настройте манифестные файлы вашего приложения следующим образом.

Объявите свой CarappService

Хост подключается к вашему приложению с помощью вашей реализации CarAppService . Вы объявляете эту услугу в своем манифесте, чтобы хост обнаружил и подключился к вашему приложению.

Вам также необходимо объявить категорию вашего приложения в элементе <category> Filter вашего приложения. См. Список категорий поддерживаемых приложений для значений, разрешенных для этого элемента.

Следующий фрагмент кода показывает, как объявить сервис приложений для автомобилей для приложения для интересующей точки в своем манифесте:

<application>
    ...
   <service
       ...
        android:name=".MyCarAppService"
        android:exported="true">
      <intent-filter>
        <action android:name="androidx.car.app.CarAppService"/>
        <category android:name="androidx.car.app.category.POI"/>
      </intent-filter>
    </service>

    ...
<application>

Поддерживаемые категории приложений

Объявите категорию вашего приложения, добавив одно или несколько из следующих значений категории в фильтр намерения, когда вы объявляете свой CarAppService , как описано в предыдущем разделе :

См. Качество приложения Android для автомобилей для подробного описания каждой категории и критериев для приложений для них.

Укажите имя приложения и значок

Вам необходимо указать имя приложения и значок, который хост может использовать для представления вашего приложения в пользовательском интерфейсе System.

Вы можете указать имя приложения и значок, который используется для представления вашего приложения, используя атрибуты label и icon вашего CarAppService :

...
<service
   android:name=".MyCarAppService"
   android:exported="true"
   android:label="@string/my_app_name"
   android:icon="@drawable/my_app_icon">
   ...
</service>
...

Если этикетка или значок не объявляются в элементе <service> , хост возвращается к значениям, указанным для элемента <application> .

Установите пользовательскую тему

Чтобы установить пользовательскую тему для вашего автомобильного приложения, добавьте элемент <meta-data> в свой манифестный файл следующим образом:

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

Затем объявите свой ресурс стиля , чтобы установить следующие атрибуты для вашей темы приложения пользователя:

<resources>
  <style name="MyCarAppTheme">
    <item name="carColorPrimary">@layout/my_primary_car_color</item>
    <item name="carColorPrimaryDark">@layout/my_primary_dark_car_color</item>
    <item name="carColorSecondary">@layout/my_secondary_car_color</item>
    <item name="carColorSecondaryDark">@layout/my_secondary_dark_car_color</item>
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

API CAR API

Библиотека приложений CAR определяет свои собственные уровни API, чтобы вы могли знать, какие функции библиотеки поддерживаются хостом шаблона на автомобиле. Чтобы получить самый высокий уровень API приложения CAR, поддерживаемый хостом, используйте метод getCarAppApiLevel() .

Объявите минимальный уровень API API приложения автомобиля, поддерживаемый вашим приложением в вашем файле AndroidManifest.xml :

<manifest ...>
    <application ...>
        <meta-data
            android:name="androidx.car.app.minCarApiLevel"
            android:value="1"/>
    </application>
</manifest>

См. Документацию для аннотации RequiresCarApi для получения подробной информации о том, как поддерживать обратную совместимость и объявить минимальный уровень API, необходимый для использования функции. Для определения того, какой уровень API необходим для использования определенной функции библиотеки приложений CAR, проверьте эталонную документацию для CarAppApiLevels .

Создайте свой CarappService и сеанс

Ваше приложение должно расширить класс CarAppService и реализовать его метод onCreateSession , который возвращает экземпляр Session , соответствующий текущему соединению с хостом:

Котлин

class HelloWorldService : CarAppService() {
    ...
    override fun onCreateSession(): Session {
        return HelloWorldSession()
    }
    ...
}

Ява

public final class HelloWorldService extends CarAppService {
    ...
    @Override
    @NonNull
    public Session onCreateSession() {
        return new HelloWorldSession();
    }
    ...
}

Экземпляр Session отвечает за возврат экземпляра Screen для использования приложения в первый раз:

Котлин

class HelloWorldSession : Session() {
    ...
    override fun onCreateScreen(intent: Intent): Screen {
        return HelloWorldScreen(carContext)
    }
    ...
}

Ява

public final class HelloWorldSession extends Session {
    ...
    @Override
    @NonNull
    public Screen onCreateScreen(@NonNull Intent intent) {
        return new HelloWorldScreen(getCarContext());
    }
    ...
}

Для обработки сценариев, когда ваш автомобильный приложение должно запустить с экрана, который не является домашним или посадочным экраном вашего приложения, такого как обработка глубоких ссылок, вы можете предварительно воспринимать заднюю стопку экранов, используя ScreenManager.push , прежде чем вернуться из onCreateScreen . Предварительное семью позволяет пользователям перейти обратно на предыдущие экраны с первого экрана, который показывает ваше приложение.

Создайте свой стартовый экран

Вы создаете экраны, отображаемые вашим приложением, определяя классы, которые расширяют класс Screen и реализуя его метод onGetTemplate , который возвращает экземпляр Template , представляющий состояние пользовательского интерфейса для отображения на экране автомобиля.

Следующий фрагмент показывает, как объявить Screen , который использует шаблон PaneTemplate для отображения простого «Hello World!» нить:

Котлин

class HelloWorldScreen(carContext: CarContext) : Screen(carContext) {
    override fun onGetTemplate(): Template {
        val row = Row.Builder().setTitle("Hello world!").build()
        val pane = Pane.Builder().addRow(row).build()
        return PaneTemplate.Builder(pane)
            .setHeaderAction(Action.APP_ICON)
            .build()
    }
}

Ява

public class HelloWorldScreen extends Screen {
    @NonNull
    @Override
    public Template onGetTemplate() {
        Row row = new Row.Builder().setTitle("Hello world!").build();
        Pane pane = new Pane.Builder().addRow(row).build();
        return new PaneTemplate.Builder(pane)
            .setHeaderAction(Action.APP_ICON)
            .build();
    }
}

Класс Carcontext

Класс CarContext - это подкласс ContextWrapper доступный для вашего Session и Screen экземпляров. Он обеспечивает доступ к автомобильным услугам, таким как ScreenManager для управления стеком экранов ; AppManager для общей функциональности, связанной с приложениями, такими как доступ к объекту Surface для рисования карт ; и NavigationManager , используемый навигационными приложениями для разворотов для передачи метаданных навигации и других событий, связанных с навигацией с хозяином.

См. Доступ к навигационным шаблонам для полного списка функций библиотеки, доступных для навигационных приложений.

CarContext также предлагает другие функциональные возможности, такие как позволение загрузке ресурсов, используемых на рисованию, используя конфигурацию с экрана CAR, запуск приложения в автомобиле , используя намерения, и сигнализировать о том, должно ли ваше приложение отображать свою карту в темной теме .

Реализовать навигацию по экрану

Приложения часто представляют ряд различных экранов, каждый из которых, возможно, использует разные шаблоны, через которые пользователь может перемещаться, когда они взаимодействуют с интерфейсом, отображаемым на экране.

Класс ScreenManager предоставляет стек экрана, который вы можете использовать для нажимания экранов, которые можно автоматически выскочить, когда пользователь выбирает кнопку на спине на экране автомобиля, или использует кнопку «Аппаратная обработка», доступная в некоторых автомобилях.

Следующий фрагмент показывает, как добавить обратное действие в шаблон сообщения, а также действие, которое нажимает на новый экран по выбору пользователем:

Котлин

val template = MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener { screenManager.push(NextScreen(carContext)) }
            .build())
    .build()

Ява

MessageTemplate template = new MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        new Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener(
                () -> getScreenManager().push(new NextScreen(getCarContext())))
            .build())
    .build();

Объект Action.BACK - это стандартное Action , которое автоматически вызывает ScreenManager.pop . Такое поведение может быть переопределено с помощью экземпляра OnBackPressedDispatcher , доступного в CarContext .

Чтобы обеспечить безопасное использование приложения во время вождения, стек экрана может иметь максимальную глубину пяти экранов. См. Раздел «Ограничения шаблона» для получения более подробной информации.

Обновить содержимое шаблона

Ваше приложение может запросить контент Screen , чтобы быть недействительным, вызывая метод Screen.invalidate . Впоследствии хост вызывает обратно на Screen.onGetTemplate вашего приложения. Метод сетки для извлечения шаблона с новым содержимым.

При обновлении Screen важно понять конкретный контент в шаблоне, который можно обновить, чтобы хост не учитывал новый шаблон против квоты шаблона. См. Раздел «Ограничения шаблона» для получения более подробной информации.

Мы рекомендовали вам структурировать свои экраны, чтобы между Screen и типом шаблона было отображение один на один и тип шаблона, который он возвращается через его реализацию onGetTemplate .

Нарисуйте карты

Навигация, точка интереса (POI) и погодные приложения с использованием следующих шаблонов могут нарисовать карты, получив доступ к Surface .

Чтобы использовать следующие шаблоны, в вашем приложении должно быть одно из соответствующих разрешений, объявленных в элементе <uses-permission> в своем файле AndroidManifest.xml .

Шаблон Разрешение шаблона Руководство категории
NavigationTemplate androidx.car.app.NAVIGATION_TEMPLATES Навигация
MapWithContentTemplate androidx.car.app.NAVIGATION_TEMPLATES или
androidx.car.app.MAP_TEMPLATES
Навигация , poi , погода
MapTemplate ( устарел ) androidx.car.app.NAVIGATION_TEMPLATES Навигация
PlaceListNavigationTemplate ( устаревший ) androidx.car.app.NAVIGATION_TEMPLATES Навигация
RoutePreviewNavigationTemplate ( устарел ) androidx.car.app.NAVIGATION_TEMPLATES Навигация

Объявить разрешение на поверхность

В дополнение к разрешению, необходимому для шаблона, который используется вами приложение, ваше приложение должно объявить разрешение androidx.car.app.ACCESS_SURFACE в своем файле AndroidManifest.xml для получения доступа к поверхности:

<manifest ...>
  ...
  <uses-permission android:name="androidx.car.app.ACCESS_SURFACE" />
  ...
</manifest>

Получить доступ к поверхности

Чтобы получить доступ к Surface , которую предоставляет хост, вы должны реализовать SurfaceCallback и предоставить эту реализацию автомобильной службе AppManager . Surface тока передается в ваш SurfaceCallback в параметре SurfaceContainer onSurfaceAvailable() и onSurfaceDestroyed() .

Котлин

carContext.getCarService(AppManager::class.java).setSurfaceCallback(surfaceCallback)

Ява

carContext.getCarService(AppManager.class).setSurfaceCallback(surfaceCallback);

Понять видимую область поверхности

Хост может нарисовать элементы пользовательского интерфейса для шаблонов в верхней части карты. Ведущий передает область поверхности, которая гарантированно будет безупречной и полностью видимой для пользователя, вызывая метод SurfaceCallback.onVisibleAreaChanged . Кроме того, чтобы свести к минимуму количество изменений, хост вызывает метод SurfaceCallback.onStableAreaChanged с наименьшим прямоугольником, который всегда виден на основе текущего шаблона.

Например, когда навигационное приложение использует NavigationTemplate с полосой действия сверху, полоска действия может скрываться, когда пользователь некоторое время не взаимодействует с экраном, чтобы уделить больше места для карты. В этом случае существует обратный вызов для onStableAreaChanged и onVisibleAreaChanged с тем же прямоугольником. Когда полоска действия скрыта, только onVisibleAreaChanged вызывается с большей площадью. Если пользователь взаимодействует с экраном, то опять же, только onVisibleAreaChanged вызывается с первым прямоугольником.

Поддержка темной темы

Приложения должны перерисовать свою карту на экземпляр Surface с помощью соответствующих темных цветов, когда хост определяет условия, которые требуют ее, как описано в качеством приложения Android для автомобилей .

Чтобы решить, нарисовать ли темную карту, вы можете использовать метод CarContext.isDarkMode . Всякий раз, когда меняется статус темной темы, вы получаете звонок на Session.onCarConfigurationChanged .

Позвольте пользователям взаимодействовать с вашей картой

При использовании следующих шаблонов вы можете добавить поддержку для пользователей для взаимодействия с картами, которые вы рисуете, например, позволить им увидеть разные части карты путем масштабирования и панорамирования.

Шаблон Интерактивность поддерживается со времени уровня API CAR API
NavigationTemplate 2
PlaceListNavigationTemplate ( устаревший ) 4
RoutePreviewNavigationTemplate ( устарел ) 4
MapTemplate ( устарел ) 5 (Введение шаблона)
MapWithContentTemplate 7 (Введение шаблона)

Реализовать обратные вызовы интерактивности

Интерфейс SurfaceCallback имеет несколько методов обратного вызова, которые вы можете реализовать, чтобы добавить интерактивность к картам, созданным с шаблонами в предыдущем разделе:

Взаимодействие Метод SurfaceCallback Поддерживается со времени уровня API CAR API
Кран onClick 5
Ущипнуть, чтобы увеличить onScale 2
Одностороннее сопротивление onScroll 2
Одиночный бросок onFling 2
Двойной таблица onScale (с масштабным фактором, определяемым хостом шаблона) 2
Вращение в режиме PAN в режиме onScroll (с коэффициентом расстояния, определяемым хостом шаблона) 2

Добавить полоску на карту

Эти шаблоны могут иметь полосу действия карты для действий, связанных с картой, такими как увеличение и выезд, накопление, отображение компаса и другие действия, которые вы выбираете для отображения. Полоса действия карты может иметь до четырех кнопок только для значков, которые можно обновить без влияния глубины задачи. Он прячется во время холостого хода и появляется в восторге от активного состояния.

Чтобы получить обратные вызовы интерактивности карты, вы должны добавить кнопку Action.PAN в полосу действия карты. Когда пользователь нажимает кнопку PAN, хост входит в режим PAN, как описано в следующем разделе.

Если в вашем приложении пропускается кнопка Action.PAN в полосе действия карты, оно не получает пользовательский ввод от методов SurfaceCallback , а хост выходит из любого ранее активированного режима PAN.

На сенсорном экране кнопка PAN не отображается.

Понять режим PAN

В режиме PAN хост шаблона переводит пользовательский ввод с не касательных устройств ввода, таких как вращающиеся контроллеры и сенсорные панели, в соответствующие методы SurfaceCallback . Ответьте на действие пользователя, чтобы ввести или выйти в режим PAN с помощью метода setPanModeListener в NavigationTemplate.Builder . Хост может скрывать другие компоненты пользовательского интерфейса в шаблоне, пока пользователь находится в режиме PAN.

Взаимодействовать с пользователем

Ваше приложение может взаимодействовать с пользователем, используя шаблоны, аналогичные мобильному приложению.

Обрабатывать пользовательский ввод

Ваше приложение может ответить на ввод пользователя, передавая соответствующих слушателей моделям, которые их поддерживают. Следующий фрагмент показывает, как создать модель Action , которая устанавливает OnClickListener , который вызывает метод, определяемый кодом вашего приложения:

Котлин

val action = Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(::onClickNavigate)
    .build()

Ява

Action action = new Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(this::onClickNavigate)
    .build();

Метод onClickNavigate может затем запустить приложение для навигации по умолчанию с помощью метода CarContext.startCarApp :

Котлин

private fun onClickNavigate() {
    val intent = Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address))
    carContext.startCarApp(intent)
}

Ява

private void onClickNavigate() {
    Intent intent = new Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address));
    getCarContext().startCarApp(intent);
}

Для получения более подробной информации о том, как запустить приложения, включая формат намерения ACTION_NAVIGATE , см. Приложение Start A Car с разделом намерения .

Некоторые действия, такие как те, которые требуют направления пользователя, чтобы продолжить взаимодействие на своих мобильных устройствах, разрешены только тогда, когда автомобиль припаркован. Вы можете использовать ParkedOnlyOnClickListener для реализации этих действий. Если автомобиль не припаркован, хост отображает пользователю указание о том, что действие не разрешено в этом случае. Если автомобиль припаркован, код выполняется нормально. Следующий фрагмент показывает, как использовать ParkedOnlyOnClickListener , чтобы открыть экран настроек на мобильном устройстве:

Котлин

val row = Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(::openSettingsOnPhone))
    .build()

Ява

Row row = new Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(this::openSettingsOnPhone))
    .build();

Отображать уведомления

Уведомления, отправленные на мобильное устройство, отображаются только на экране автомобиля, если они расширены с помощью CarAppExtender . Некоторые атрибуты уведомлений, такие как заголовок контента, текст, значок и действия, могут быть установлены в CarAppExtender , переопределяя атрибуты уведомления, когда они появляются на экране автомобиля.

Следующий фрагмент показывает, как отправить уведомление на экран автомобиля, который отображает другой заголовок, чем указанный на мобильном устройстве:

Котлин

val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build()

Ява

Notification notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        new CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build();

Уведомления могут повлиять на следующие части пользовательского интерфейса:

  • Уведомление Heads-Up (HUN) может отображаться пользователю.
  • Можно добавить запись в центре уведомлений, при желании со знаком, видимым на рельсе.
  • Для приложений для навигации уведомление может отображаться в виджете рельса, как описано в по очереди уведомлений .

Вы можете выбрать, как настроить уведомления вашего приложения, чтобы повлиять на каждый из этих элементов пользовательского интерфейса, используя приоритет уведомления, как описано в документации CarAppExtender .

Если NotificationCompat.Builder.setOnlyAlertOnce вызывается со значением true , уведомление о высокоприорителе отображается как гунна только один раз.

Для получения дополнительной информации о том, как разработать уведомления вашего автомобиля, см. Руководство по проектированию Google для вождения об уведомлениях .

Показывать тосты

Ваше приложение может отображать тост, используя CarToast как показано в этом фрагменте:

Котлин

CarToast.makeText(carContext, "Hello!", CarToast.LENGTH_SHORT).show()

Ява

CarToast.makeText(getCarContext(), "Hello!", CarToast.LENGTH_SHORT).show();

Запросить разрешения

Если ваше приложение нуждается в доступе к ограниченным данным или действиям - например, местоположение - применяются стандартные правила разрешений на андроид . Чтобы запросить разрешение, вы можете использовать метод CarContext.requestPermissions() .

Преимущество использования CarContext.requestPermissions() , в отличие от использования стандартных API Android , заключается в том, что вам не нужно запускать собственную Activity , чтобы создать диалог разрешений. Кроме того, вы можете использовать один и тот же код на Android Auto Auto и Android Automotive OS, а не для создания платформенных потоков.

Стиль диалог разрешений на Android Auto

На Android Auto диалог разрешений для пользователя появится на телефоне. По умолчанию за диалогом не будет никакого фона. Чтобы установить пользовательский фон, объявите тему приложения CAR в вашем файле AndroidManifest.xml и установите атрибут carPermissionActivityLayout для вашей темы приложения вашего автомобиля.

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

Затем установите атрибут carPermissionActivityLayout для темы вашего приложения вашего автомобиля:

<resources>
  <style name="MyCarAppTheme">
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

Запустить автомобильное приложение с намерения

Вы можете позвонить в метод CarContext.startCarApp для выполнения одного из следующих действий:

В следующем примере показано, как создать уведомление с действием, которое открывает ваше приложение с экраном, которое показывает детали бронирования парковки. Вы расширяете экземпляр уведомления с целью контента, который содержит PendingIntent обертывание явного намерения к действию вашего приложения:

Котлин

val notification = notificationBuilder
    ...
    .extend(
        CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(ComponentName(context, MyNotificationReceiver::class.java)),
                    0))
            .build())

Ява

Notification notification = notificationBuilder
    ...
    .extend(
        new CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    new Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(new ComponentName(context, MyNotificationReceiver.class)),
                    0))
            .build());

Ваше приложение также должно объявить о BroadcastReceiver , что для обработки намерения, когда пользователь выбирает действие в интерфейсе уведомлений, и вызывает CarContext.startCarApp с намерением, включая URI:

Котлин

class MyNotificationReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val intentAction = intent.action
        if (ACTION_VIEW_PARKING_RESERVATION == intentAction) {
            CarContext.startCarApp(
                intent,
                Intent(Intent.ACTION_VIEW)
                    .setComponent(ComponentName(context, MyCarAppService::class.java))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)))
        }
    }
}

Ява

public class MyNotificationReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String intentAction = intent.getAction();
        if (ACTION_VIEW_PARKING_RESERVATION.equals(intentAction)) {
            CarContext.startCarApp(
                intent,
                new Intent(Intent.ACTION_VIEW)
                    .setComponent(new ComponentName(context, MyCarAppService.class))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)));
        }
    }
}

Наконец, метод Session.onNewIntent в вашем приложении обрабатывает это намерение, нажимая экран резервирования парковки в стеке, если он еще не наверху:

Котлин

override fun onNewIntent(intent: Intent) {
    val screenManager = carContext.getCarService(ScreenManager::class.java)
    val uri = intent.data
    if (uri != null
        && MY_URI_SCHEME == uri.scheme
        && MY_URI_HOST == uri.schemeSpecificPart
        && ACTION_VIEW_PARKING_RESERVATION == uri.fragment
    ) {
        val top = screenManager.top
        if (top !is ParkingReservationScreen) {
            screenManager.push(ParkingReservationScreen(carContext))
        }
    }
}

Ява

@Override
public void onNewIntent(@NonNull Intent intent) {
    ScreenManager screenManager = getCarContext().getCarService(ScreenManager.class);
    Uri uri = intent.getData();
    if (uri != null
        && MY_URI_SCHEME.equals(uri.getScheme())
        && MY_URI_HOST.equals(uri.getSchemeSpecificPart())
        && ACTION_VIEW_PARKING_RESERVATION.equals(uri.getFragment())
    ) {
        Screen top = screenManager.getTop();
        if (!(top instanceof ParkingReservationScreen)) {
            screenManager.push(new ParkingReservationScreen(getCarContext()));
        }
    }
}

См. Раздел «Уведомления о дисплее» для получения дополнительной информации о том, как обрабатывать уведомления для приложения CAR.

Ограничения шаблона

Хост ограничивает количество шаблонов для отображения для данной задачи максимум пять, из которых последний шаблон должен быть одним из следующих типов:

Обратите внимание, что этот предел относится к количеству шаблонов, а не к количеству экземпляров Screen в стеке. Например, если приложение отправляет два шаблона, находясь на экране A, а затем нажимает экран B, теперь оно может отправить еще три шаблона. В качестве альтернативы, если каждый экран структурирован для отправки одного шаблона, то приложение может нажать пять экземпляров экрана в стек ScreenManager .

Существуют особые случаи в этих ограничениях: обновления шаблона и обратно и сбросить операции.

Шаблон обновляется

Определенные обновления контента не учитываются до предела шаблона. В целом, если приложение выдвигает новый шаблон, который имеет один и тот же тип и содержит тот же основной контент, что и предыдущий шаблон, новый шаблон не учитывается против квоты. Например, обновление состояния переключения строки в ListTemplate не учитывается против квоты. Посмотрите документацию отдельных шаблонов, чтобы узнать больше о том, какие типы обновлений контента можно считать обновлением.

Обратные операции

Чтобы включить подборы в задаче, хост обнаруживает, когда приложение вынимает Screen из стека ScreenManager и обновляет оставшуюся квоту на основе количества шаблонов, которые приложение идет назад.

Например, если приложение отправляет два шаблона на экране A, то нажимает экран B и отправляет еще два шаблона, приложение остается одна квота. Если приложение затем отображается на экран A, хост сбрасывает квоту на три, потому что приложение пошло назад на два шаблона.

Обратите внимание, что при возвращении на экран приложение должно отправить шаблон, который имеет тот же тип, что и последний, отправленный этим экраном. Отправка любого другого типа шаблона вызывает ошибку. Однако до тех пор, пока тип остается таким же во время операции на заднем плане, приложение может свободно изменять содержимое шаблона, не влияя на квоту.

Сбросить операции

Определенные шаблоны имеют специальную семантику, которая обозначает конец задачи. Например, NavigationTemplate -это представление, которое, как ожидается, останется на экране и обновляется новыми инструкциями по поводу потребления пользователя. Когда он достигает одного из этих шаблонов, хост сбрасывает квоту шаблона, обрабатывая этот шаблон, как будто это первый шаг новой задачи. Это позволяет приложению начать новую задачу. Смотрите документацию отдельных шаблонов, чтобы увидеть, какие из них запускают сброс на хосте.

Если хост получает намерение запустить приложение из действия уведомления или с запуска, квота также сбросится. Этот механизм позволяет приложению начинать новую задачу по течению из уведомлений, и оно сохраняет правду, даже если приложение уже связано и на переднем плане.

См. Раздел «Уведомления о дисплее» для получения более подробной информации о том, как отобразить уведомления вашего приложения на экране автомобиля. См. Приложение Start A Car с разделом намерений для получения информации о том, как запустить ваше приложение с действия уведомления.

Соединение API

Вы можете определить, работает ли ваше приложение на Android Auto или Android Automotive OS, используя API CarConnection для получения информации об соединении во время выполнения.

Например, в Session вашего приложения вашего автомобиля инициализируйте CarConnection и подпишитесь на обновления LiveData :

Котлин

CarConnection(carContext).type.observe(this, ::onConnectionStateUpdated)

Ява

new CarConnection(getCarContext()).getType().observe(this, this::onConnectionStateUpdated);

На наблюдателе вы можете отреагировать на изменения в состоянии соединения:

Котлин

fun onConnectionStateUpdated(connectionState: Int) {
  val message = when(connectionState) {
    CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not connected to a head unit"
    CarConnection.CONNECTION_TYPE_NATIVE -> "Connected to Android Automotive OS"
    CarConnection.CONNECTION_TYPE_PROJECTION -> "Connected to Android Auto"
    else -> "Unknown car connection type"
  }
  CarToast.makeText(carContext, message, CarToast.LENGTH_SHORT).show()
}

Ява

private void onConnectionStateUpdated(int connectionState) {
  String message;
  switch(connectionState) {
    case CarConnection.CONNECTION_TYPE_NOT_CONNECTED:
      message = "Not connected to a head unit";
      break;
    case CarConnection.CONNECTION_TYPE_NATIVE:
      message = "Connected to Android Automotive OS";
      break;
    case CarConnection.CONNECTION_TYPE_PROJECTION:
      message = "Connected to Android Auto";
      break;
    default:
      message = "Unknown car connection type";
      break;
  }
  CarToast.makeText(getCarContext(), message, CarToast.LENGTH_SHORT).show();
}

Ограничения API

Различные автомобили могут разрешить различное количество экземпляров Item для пользователя за раз. Используйте ConstraintManager , чтобы проверить ограничение содержимого во время выполнения и установить соответствующее количество элементов в ваших шаблонах.

Начните с получения CarContext ConstraintManager :

Котлин

val manager = carContext.getCarService(ConstraintManager::class.java)

Ява

ConstraintManager manager = getCarContext().getCarService(ConstraintManager.class);

Затем вы можете запрашивать полученный объект ConstraintManager для соответствующего ограничения контента. Например, чтобы получить количество элементов, которые можно отобразить в сетке, вызовите getContentLimit с CONTENT_LIMIT_TYPE_GRID :

Котлин

val gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID)

Ява

int gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID);

Добавить поток входа

Если ваше приложение предлагает подписанный опыт для пользователей, вы можете использовать такие шаблоны, как The SignInTemplate и LongMessageTemplate с API API CAR, для обработки регистрации в вашем приложении в головном устройстве автомобиля.

Чтобы создать SignInTemplate , определите SignInMethod . Библиотека приложений CAR в настоящее время поддерживает следующие методы входа:

  • InputSignInMethod для входа имени пользователя/пароля.
  • PinSignInMethod для входа в систему, где пользователь связывает свою учетную запись со своего телефона, используя PIN-код, отображаемый на головном блоке.
  • ProviderSignInMethod для входа провайдера, таких как вход Google и один TAP .
  • QRCodeSignInMethod для QR-кода входите, где пользователь сканирует QR-код для завершения входа на свой телефон. Это доступно с автомобильным API уровнем 4 и выше.

Например, чтобы реализовать шаблон, который собирает пароль пользователя, начните с создания InputCallback для обработки и проверки пользовательского ввода:

Котлин

val callback = object : InputCallback {
    override fun onInputSubmitted(text: String) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    override fun onInputTextChanged(text: String) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
}

Ява

InputCallback callback = new InputCallback() {
    @Override
    public void onInputSubmitted(@NonNull String text) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    @Override
    public void onInputTextChanged(@NonNull String text) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
};

InputCallback требуется для InputSignInMethod Builder .

Котлин

val passwordInput = InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build()

Ява

InputSignInMethod passwordInput = new InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build();

Наконец, используйте свой новый InputSignInMethod , чтобы создать SignInTemplate .

Котлин

SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build()

Ява

new SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build();

Используйте AccountManager

Приложения Android Automotive OS, которые имеют аутентификацию, должны использовать AccountManager по следующим причинам:

  • Лучше UX и простота управления учетными записями : пользователи могут легко управлять всеми своими учетными записями из меню учетных записей в настройках системы, включая вход и регистрацию.
  • Опыт «гостя» : поскольку автомобили являются общими устройствами, производители могут дать возможность гостям в автомобиле, где счета не могут быть добавлены.

Добавить варианты текстовых строк

Different car screen sizes may show different amounts of text. With Car App API level 2 and above, you can specify multiple variants of a text string to best fit the screen. To see where text variants are accepted, look for templates and components that take a CarText .

You can add text string variants to a CarText with the CarText.Builder.addVariant() method:

Котлин

val itemTitle = CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build()

Ява

CarText itemTitle = new CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build();

You can then use this CarText —for example, as the primary text of a GridItem .

Котлин

GridItem.Builder()
    .addTitle(itemTitle)
    ...
    .build()

Ява

new GridItem.Builder()
    .addTitle(itemTitle)
    ...
    build();

Add strings in order from most to least preferred—for example, from longest to shortest. The host picks the appropriate-length string depending on the amount of space available on the car screen.

Add inline CarIcons for rows

You can add icons inline with text to enrich your app's visual appeal using CarIconSpan . See the documentation for CarIconSpan.create for more information on creating these spans. See Spantastic text styling with Spans for an overview of how text styling with spans work.

Котлин

  
val rating = SpannableString("Rating: 4.5 stars")
rating.setSpan(
    CarIconSpan.create(
        // Create a CarIcon with an image of four and a half stars
        CarIcon.Builder(...).build(),
        // Align the CarIcon to the baseline of the text
        CarIconSpan.ALIGN_BASELINE
    ),
    // The start index of the span (index of the character '4')
    8,
    // The end index of the span (index of the last 's' in "stars")
    16,
    Spanned.SPAN_INCLUSIVE_INCLUSIVE
)

val row = Row.Builder()
    ...
    .addText(rating)
    .build()
  
  

Ява

  
SpannableString rating = new SpannableString("Rating: 4.5 stars");
rating.setSpan(
        CarIconSpan.create(
                // Create a CarIcon with an image of four and a half stars
                new CarIcon.Builder(...).build(),
                // Align the CarIcon to the baseline of the text
                CarIconSpan.ALIGN_BASELINE
        ),
        // The start index of the span (index of the character '4')
        8,
        // The end index of the span (index of the last 's' in "stars")
        16,
        Spanned.SPAN_INCLUSIVE_INCLUSIVE
);
Row row = new Row.Builder()
        ...
        .addText(rating)
        .build();
  
  

Car Hardware APIs

Starting with Car App API level 3, the Car App Library has APIs that you can use to access vehicle properties and sensors.

Требования

To use the APIs with Android Auto, start by adding a dependency on androidx.car.app:app-projected to the build.gradle file for your Android Auto module. For Android Automotive OS, add a dependency on androidx.car.app:app-automotive to the build.gradle file for your Android Automotive OS module.

Additionally, in your AndroidManifest.xml file, you need to declare the relevant permissions needed to request the car data you want to use. Note that these permissions must also be granted to you by the user. You can use the same code on both Android Auto and Android Automotive OS, rather than having to create platform-dependent flows. However, the permissions needed are different.

CarInfo

This table describes the properties surfaced by the CarInfo APIs and the permissions you need to request to use them:

Методы Характеристики Android Auto Permissions Android Automotive OS Permissions Supported since Car App API level
fetchModel Make, model, year android.car.permission.CAR_INFO 3
fetchEnergyProfile EV connector types, fuel types com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_INFO 3
fetchExteriorDimensions

This data is only available on some Android Automotive OS vehicles running API 30 or higher

Exterior dimensions Н/Д android.car.permission.CAR_INFO 7
addTollListener
removeTollListener
Toll card state, toll card type 3
addEnergyLevelListener
removeEnergyLevelListener
Battery level, fuel level, fuel level low, range remaining com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_ENERGY ,
android.car.permission.CAR_ENERGY_PORTS ,
android.car.permission.READ_CAR_DISPLAY_UNITS
3
addSpeedListener
removeSpeedListener
Raw speed, display speed (shown on car's cluster display) com.google.android.gms.permission.CAR_SPEED android.car.permission.CAR_SPEED ,
android.car.permission.READ_CAR_DISPLAY_UNITS
3
addMileageListener
removeMileageListener

Warning: the getOdometerMeters method of the Mileage class is inaccurately named and returns kilometers, not meters.

Odometer distance com.google.android.gms.permission.CAR_MILEAGE This data is not available on Android Automotive OS to apps installed from the Play Store. 3

For example, to get the remaining range, instantiate a CarInfo object, then create and register an OnCarDataAvailableListener :

Котлин

val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo

val listener = OnCarDataAvailableListener<EnergyLevel> { data ->
    if (data.rangeRemainingMeters.status == CarValue.STATUS_SUCCESS) {
      val rangeRemaining = data.rangeRemainingMeters.value
    } else {
      // Handle error
    }
  }

carInfo.addEnergyLevelListener(carContext.mainExecutor, listener)

// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener)

Ява

CarInfo carInfo = getCarContext().getCarService(CarHardwareManager.class).getCarInfo();

OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
  if(data.getRangeRemainingMeters().getStatus() == CarValue.STATUS_SUCCESS) {
    float rangeRemaining = data.getRangeRemainingMeters().getValue();
  } else {
    // Handle error
  }
};

carInfo.addEnergyLevelListener(getCarContext().getMainExecutor(), listener);

// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener);

Don't assume that the data from the car is available at all times. If you get an error, check the status of the value you requested to better understand why the data you requested could not be retrieved. Refer to the reference documentation for the full CarInfo class definition.

CarSensors

The CarSensors class gives you access to the vehicle's accelerometer, gyroscope, compass, and location data. The availability of these values may depend on the OEM. The format for the data from the accelerometer, gyroscope, and compass is the same as you would get from the SensorManager API . For example, to check the vehicle's heading:

Котлин

val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors

val listener = OnCarDataAvailableListener<Compass> { data ->
    if (data.orientations.status == CarValue.STATUS_SUCCESS) {
      val orientation = data.orientations.value
    } else {
      // Data not available, handle error
    }
  }

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, carContext.mainExecutor, listener)

// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener)

Ява

CarSensors carSensors = getCarContext().getCarService(CarHardwareManager.class).getCarSensors();

OnCarDataAvailableListener<Compass> listener = (data) -> {
  if (data.getOrientations().getStatus() == CarValue.STATUS_SUCCESS) {
    List<Float> orientations = data.getOrientations().getValue();
  } else {
    // Data not available, handle error
  }
};

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, getCarContext().getMainExecutor(),
    listener);

// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener);

To access location data from the car, you also need to declare and request the android.permission.ACCESS_FINE_LOCATION permission.

Тестирование

To simulate sensor data when testing on Android Auto, refer to the Sensors and Sensor configuration sections of the Desktop Head Unit guide. To simulate sensor data when testing on Android Automotive OS, refer to the Emulate hardware state section of the Android Automotive OS emulator guide.

The CarAppService, Session and Screen lifecycles

The Session and Screen classes implement the LifecycleOwner interface. As the user interacts with the app, your Session and Screen objects' lifecycle callbacks are invoked, as described in the following diagrams.

The lifecycles of a CarAppService and a Session

Figure 1 . The Session lifecycle.

For full details, see the documentation for the Session.getLifecycle method.

The lifecycle of a Screen

Figure 2 . The Screen lifecycle.

For full details, see the documentation for the Screen.getLifecycle method.

Record from the car microphone

Using your app's CarAppService and the CarAudioRecord API, you can give your app access to the user's car microphone. Users need to give your app permission to access the car microphone. Your app can record and process the user's input within your app.

Permission to record

Before recording any audio, you must first declare the permission to record in your AndroidManifest.xml and request that the user grant it.

<manifest ...>
   ...
   <uses-permission android:name="android.permission.RECORD_AUDIO" />
   ...
</manifest>

You need to request the permission to record at runtime. See the Request permissions section for details on how to request a permission in your car app.

Record audio

After the user gives permission to record, you can record the audio and process the recording.

Котлин

val carAudioRecord = CarAudioRecord.create(carContext)
        carAudioRecord.startRecording()

        val data = ByteArray(CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE)
        while(carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording()
 

Ява

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        carAudioRecord.startRecording();

        byte[] data = new byte[CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE];
        while (carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording();
 

Audio focus

When recording from the car microphone, first acquire audio focus to ensure that any ongoing media is stopped. If you lose audio focus, stop recording.

Here is an example of how to acquire audio focus:

Котлин

 
val carAudioRecord = CarAudioRecord.create(carContext)
        
        // Take audio focus so that user's media is not recorded
        val audioAttributes = AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
            // Use the most appropriate usage type for your use case
            .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
            .build()
        
        val audioFocusRequest =
            AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                .setAudioAttributes(audioAttributes)
                .setOnAudioFocusChangeListener { state: Int ->
                    if (state == AudioManager.AUDIOFOCUS_LOSS) {
                        // Stop recording if audio focus is lost
                        carAudioRecord.stopRecording()
                    }
                }
                .build()
        
        if (carContext.getSystemService(AudioManager::class.java)
                .requestAudioFocus(audioFocusRequest)
            != AudioManager.AUDIOFOCUS_REQUEST_GRANTED
        ) {
            // Don't record if the focus isn't granted
            return
        }
        
        carAudioRecord.startRecording()
        // Process the audio and abandon the AudioFocusRequest when done

Ява

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        // Take audio focus so that user's media is not recorded
        AudioAttributes audioAttributes =
                new AudioAttributes.Builder()
                        .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                        // Use the most appropriate usage type for your use case
                        .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
                        .build();

        AudioFocusRequest audioFocusRequest =
                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                        .setAudioAttributes(audioAttributes)
                        .setOnAudioFocusChangeListener(state -> {
                            if (state == AudioManager.AUDIOFOCUS_LOSS) {
                                // Stop recording if audio focus is lost
                                carAudioRecord.stopRecording();
                            }
                        })
                        .build();

        if (getCarContext().getSystemService(AudioManager.class).requestAudioFocus(audioFocusRequest)
                != AUDIOFOCUS_REQUEST_GRANTED) {
            // Don't record if the focus isn't granted
            return;
        }

        carAudioRecord.startRecording();
        // Process the audio and abandon the AudioFocusRequest when done
 

Testing Library

The Android for Cars Testing Library provides auxiliary classes that you can use to validate your app's behavior in a test environment. For example, the SessionController lets you simulate a connection to the host and verify that the correct Screen and Template are created and returned.

Refer to the Samples for usage examples.

Report an Android for Cars App Library issue

If you find an issue with the library, report it using the Google Issue Tracker . Be sure to fill out all the requested information in the issue template.

Create a new issue

Before filing a new issue, please check whether it is listed in the library's release notes or reported in the issues list. You can subscribe and vote for issues by clicking the star for an issue in the tracker. For more information, see Subscribing to an Issue .

,

The Android for Cars App Library lets you bring your navigation , point of interest (POI) , internet of things (IOT) , or weather app to the car. It does so by providing a set of templates designed to meet driver distraction standards and taking care of details like the variety of car screen factors and input modalities.

This guide provides an overview of the library's key features and concepts and walks you through the process of setting up a basic app.

Прежде чем начать

  1. Review the Design for Driving pages covering the Car App Library
  2. Review the key terms and concepts in the following section.
  3. Familiarize yourself with the Android Auto System UI and Android Automotive OS design .
  4. Review the Release Notes .
  5. Review the Samples .

Key terms and concepts

Models and Templates
The user interface is represented by a graph of model objects that can be arranged together in different ways, as allowed by the template they belong to. Templates are a subset of the models that can act as a root in those graphs. Models include the information to be displayed to the user in the form of text and images as well as attributes to configure aspects of the visual appearance of such information—for example, text colors or image sizes. The host converts the models to views that are designed to meet driver distraction standards and takes care of details like the variety of car screen factors and input modalities.
Хозяин
The host is the backend component that implements the functionality offered by the library's APIs so your app can run in the car. The responsibilities of the host range from discovering your app and managing its lifecycle to converting your models into views and notifying your app of user interactions. On mobile devices, this host is implemented by Android Auto. On Android Automotive OS, this host is installed as a system app.
Template restrictions
Different templates enforce restrictions in the content of their models. For example, list templates have limits on the number of items that can be presented to the user. Templates also have restrictions in the way they can be connected to form the flow of a task. For example, the app can only push up to five templates to the screen stack. See Template restrictions for more details.
Screen
Screen is a class provided by the library that apps implement to manage the user interface presented to the user. A Screen has a lifecycle and provides the mechanism for the app to send the template to display when the screen is visible. Screen instances can also be pushed and popped to and from a Screen stack , which ensures they adhere to the template flow restrictions .
CarAppService
CarAppService is an abstract Service class that your app must implement and export to be discovered and managed by the host. Your app's CarAppService is responsible for validating that a host connection can be trusted using createHostValidator and subsequently providing Session instances for each connection using onCreateSession .
Session

Session is an abstract class that your app must implement and return using CarAppService.onCreateSession . It serves as the entry point to display information on the car screen. It has a lifecycle that informs the current state of your app on the car screen, such as when your app is visible or hidden.

When a Session is started, such as when the app is first launched, the host requests for the initial Screen to display using the onCreateScreen method.

Install the Car App Library

See the Jetpack library release page for instructions on how to add the library to your app.

Configure your app's manifest files

Before you can create your car app, configure your app's manifest files as follows.

Declare your CarAppService

The host connects to your app through your CarAppService implementation. You declare this service in your manifest to let the host discover and connect to your app.

You also need to declare your app's category in the <category> element of your app's intent filter. See the list of supported app categories for the values allowed for this element.

The following code snippet shows how to declare a car app service for a point of interest app in your manifest:

<application>
    ...
   <service
       ...
        android:name=".MyCarAppService"
        android:exported="true">
      <intent-filter>
        <action android:name="androidx.car.app.CarAppService"/>
        <category android:name="androidx.car.app.category.POI"/>
      </intent-filter>
    </service>

    ...
<application>

Supported app categories

Declare your app's category by adding one or more of the following category values in the intent filter when you declare your CarAppService as described in the preceding section :

  • androidx.car.app.category.NAVIGATION : an app that provides turn-by-turn navigation directions. See Build navigation apps for cars .
  • androidx.car.app.category.POI : an app that provides functionality relevant to finding points of interest such as parking spots, charging stations, and gas stations. See Build point of interest apps for cars .
  • androidx.car.app.category.IOT : an app that enables users to take relevant actions on connected devices from within the car. See Build internet of things apps for cars .
  • androidx.car.app.category.WEATHER : an app that lets users see relevant weather information related to their current location or along their route. See Build weather apps for cars .

See Android app quality for cars for detailed descriptions of each category and criteria for apps to belong to them.

Specify the app name and icon

You need to specify an app name and icon that the host can use to represent your app in the system UI.

You can specify the app name and icon that is used to represent your app using the label and icon attributes of your CarAppService :

...
<service
   android:name=".MyCarAppService"
   android:exported="true"
   android:label="@string/my_app_name"
   android:icon="@drawable/my_app_icon">
   ...
</service>
...

If the label or icon are not declared in the <service> element, the host falls back to the values specified for the <application> element.

Set a custom theme

To set a custom theme for your car app, add a <meta-data> element in your manifest file, as follows:

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

Then, declare your style resource to set the following attributes for your custom car app theme:

<resources>
  <style name="MyCarAppTheme">
    <item name="carColorPrimary">@layout/my_primary_car_color</item>
    <item name="carColorPrimaryDark">@layout/my_primary_dark_car_color</item>
    <item name="carColorSecondary">@layout/my_secondary_car_color</item>
    <item name="carColorSecondaryDark">@layout/my_secondary_dark_car_color</item>
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

Car App API level

The Car App Library defines its own API levels so that you can know which library features are supported by the template host on a vehicle. To retrieve the highest Car App API Level supported by a host, use the getCarAppApiLevel() method.

Declare the minimum Car App API Level supported by your app in your AndroidManifest.xml file:

<manifest ...>
    <application ...>
        <meta-data
            android:name="androidx.car.app.minCarApiLevel"
            android:value="1"/>
    </application>
</manifest>

See the documentation for the RequiresCarApi annotation for details on how to maintain backward compatibility and declare the minimum API level required to use a feature. For a definition of which API level is required to use a certain feature of the Car App Library, check the reference documentation for CarAppApiLevels .

Create your CarAppService and Session

Your app needs to extend the CarAppService class and implement its onCreateSession method, which returns a Session instance corresponding to the current connection to the host:

Котлин

class HelloWorldService : CarAppService() {
    ...
    override fun onCreateSession(): Session {
        return HelloWorldSession()
    }
    ...
}

Ява

public final class HelloWorldService extends CarAppService {
    ...
    @Override
    @NonNull
    public Session onCreateSession() {
        return new HelloWorldSession();
    }
    ...
}

The Session instance is responsible for returning the Screen instance to use the first time the app is started:

Котлин

class HelloWorldSession : Session() {
    ...
    override fun onCreateScreen(intent: Intent): Screen {
        return HelloWorldScreen(carContext)
    }
    ...
}

Ява

public final class HelloWorldSession extends Session {
    ...
    @Override
    @NonNull
    public Screen onCreateScreen(@NonNull Intent intent) {
        return new HelloWorldScreen(getCarContext());
    }
    ...
}

To handle scenarios where your car app needs to start from a screen that is not the home or landing screen of your app, such as handling deep links, you can pre-seed a back stack of screens using ScreenManager.push before returning from onCreateScreen . Pre-seeding allows users to navigate back to previous screens from the first screen that your app is showing.

Create your start screen

You create the screens displayed by your app by defining classes that extend the Screen class and implementing its onGetTemplate method, which returns the Template instance representing the state of the UI to display in the car screen.

The following snippet shows how to declare a Screen that uses a PaneTemplate template to display a simple “Hello world!” нить:

Котлин

class HelloWorldScreen(carContext: CarContext) : Screen(carContext) {
    override fun onGetTemplate(): Template {
        val row = Row.Builder().setTitle("Hello world!").build()
        val pane = Pane.Builder().addRow(row).build()
        return PaneTemplate.Builder(pane)
            .setHeaderAction(Action.APP_ICON)
            .build()
    }
}

Ява

public class HelloWorldScreen extends Screen {
    @NonNull
    @Override
    public Template onGetTemplate() {
        Row row = new Row.Builder().setTitle("Hello world!").build();
        Pane pane = new Pane.Builder().addRow(row).build();
        return new PaneTemplate.Builder(pane)
            .setHeaderAction(Action.APP_ICON)
            .build();
    }
}

The CarContext class

The CarContext class is a ContextWrapper subclass accessible to your Session and Screen instances. It provides access to car services, such as the ScreenManager for managing the screen stack ; the AppManager for general app-related functionality, such as accessing the Surface object for drawing maps ; and the NavigationManager used by turn-by-turn navigation apps to communicate navigation metadata and other navigation-related events with the host.

See Access the navigation templates for a comprehensive list of library functionality available to navigation apps.

CarContext also offers other functionality, such as letting you load drawable resources using the configuration from the car screen, starting an app in the car using intents, and signaling whether your app should display its map in dark theme .

Implement screen navigation

Apps often present a number of different screens, each possibly using different templates the user can navigate through as they interact with the interface displayed in the screen.

The ScreenManager class provides a screen stack you can use to push screens that can be popped automatically when the user selects a back button in the car screen or uses the hardware back button available in some cars.

The following snippet shows how to add a back action to a message template as well as an action that pushes a new screen when selected by the user:

Котлин

val template = MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener { screenManager.push(NextScreen(carContext)) }
            .build())
    .build()

Ява

MessageTemplate template = new MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        new Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener(
                () -> getScreenManager().push(new NextScreen(getCarContext())))
            .build())
    .build();

The Action.BACK object is a standard Action that automatically invokes ScreenManager.pop . This behavior can be overridden by using the OnBackPressedDispatcher instance available from the CarContext .

To help ensure the app is safe to use while driving, the screen stack can have a maximum depth of five screens. See the Template restrictions section for more details.

Refresh the contents of a template

Your app can request the content of a Screen to be invalidated by calling the Screen.invalidate method. The host subsequently calls back into your app's Screen.onGetTemplate method to retrieve the template with the new contents.

When refreshing a Screen , it is important to understand the specific content in the template that can be updated so the host does not count the new template against the template quota. See the Template restrictions section for more details.

We recommended that you structure your screens so there is a one-to-one mapping between a Screen and the type of template it returns through its onGetTemplate implementation.

Draw maps

Navigation, point of interest (POI), and weather apps using the following templates can draw maps by accessing a Surface .

To use the following templates, your app must have one of the corresponding permissions declared in a <uses-permission> element in its AndroidManifest.xml file.

Шаблон Template permission Category guidance
NavigationTemplate androidx.car.app.NAVIGATION_TEMPLATES Навигация
MapWithContentTemplate androidx.car.app.NAVIGATION_TEMPLATES OR
androidx.car.app.MAP_TEMPLATES
Navigation , POI , Weather
MapTemplate ( deprecated ) androidx.car.app.NAVIGATION_TEMPLATES Навигация
PlaceListNavigationTemplate ( deprecated ) androidx.car.app.NAVIGATION_TEMPLATES Навигация
RoutePreviewNavigationTemplate ( deprecated ) androidx.car.app.NAVIGATION_TEMPLATES Навигация

Declare the surface permission

In addition to the permission required for the template that you app is using, your app must declare the androidx.car.app.ACCESS_SURFACE permission in its AndroidManifest.xml file to get access to the surface:

<manifest ...>
  ...
  <uses-permission android:name="androidx.car.app.ACCESS_SURFACE" />
  ...
</manifest>

Access the surface

To access the Surface that the host provides, you must implement a SurfaceCallback and provide that implementation to the AppManager car service. The current Surface is passed to your SurfaceCallback in the SurfaceContainer parameter of the onSurfaceAvailable() and onSurfaceDestroyed() callbacks.

Котлин

carContext.getCarService(AppManager::class.java).setSurfaceCallback(surfaceCallback)

Ява

carContext.getCarService(AppManager.class).setSurfaceCallback(surfaceCallback);

Understand the surface's visible area

The host can draw user interface elements for the templates on top of the map. The host communicates the area of the surface that is guaranteed to be unobstructed and fully visible to the user by calling the SurfaceCallback.onVisibleAreaChanged method. Also, to minimize the number of changes, the host calls the SurfaceCallback.onStableAreaChanged method with the smallest rectangle, which is always visible based on the current template.

For example, when a navigation app uses the NavigationTemplate with an action strip on top, the action strip can hide itself when the user has not interacted with the screen for a while to make more space for the map. In this case, there is a callback to onStableAreaChanged and onVisibleAreaChanged with the same rectangle. When the action strip is hidden, only onVisibleAreaChanged is called with the larger area. If the user interacts with the screen, then again only onVisibleAreaChanged is called with the first rectangle.

Support dark theme

Apps must redraw their map onto the Surface instance with the proper dark colors when the host determines conditions warrant it, as described in Android app quality for cars .

To decide whether to draw a dark map, you can use the CarContext.isDarkMode method. Whenever the dark theme status changes, you receive a call to Session.onCarConfigurationChanged .

Let users interact with your map

When using the following templates, you can add support for users to interact with the maps you draw, such as letting them see different parts of a map by zooming and panning.

Шаблон Interactivity supported since Car App API Level
NavigationTemplate 2
PlaceListNavigationTemplate ( deprecated ) 4
RoutePreviewNavigationTemplate ( deprecated ) 4
MapTemplate ( deprecated ) 5 (introduction of template)
MapWithContentTemplate 7 (introduction of template)

Implement interactivity callbacks

The SurfaceCallback interface has several callback methods you can implement to add interactivity to maps built with the templates in the preceding section:

Взаимодействие SurfaceCallback method Supported since Car App API level
Кран onClick 5
Pinch to zoom onScale 2
Single-touch drag onScroll 2
Single-touch fling onFling 2
Double-tap onScale (with scale factor determined by template host) 2
Rotary nudge in pan mode onScroll (with distance factor determined by template host) 2

Add a map action strip

These templates can have a map action strip for map-related actions such as zooming in and out, recentering, displaying a compass, and other actions you choose to display. The map action strip can have up to four icon-only buttons that can be refreshed without impacting task depth. It hides during idle state and reappears on active state.

To receive map interactivity callbacks , you must add an Action.PAN button in the map action strip. When the user presses the pan button, the host enters pan mode, as described in the following section.

If your app omits the Action.PAN button in the map action strip, it doesn't receive user input from the SurfaceCallback methods, and the host exits any previously activated pan mode.

On a touchscreen, the pan button is not displayed.

Understand pan mode

In pan mode, the template host translates user input from non-touch input devices, such as rotary controllers and touchpads, into the appropriate SurfaceCallback methods. Respond to the user action to enter or exit pan mode with the setPanModeListener method in the NavigationTemplate.Builder . The host can hide other UI components in the template while the user is in pan mode.

Interact with the user

Your app can interact with the user using patterns similar to a mobile app.

Handle user input

Your app can respond to user input by passing the appropriate listeners to the models that support them. The following snippet shows how to create an Action model that sets an OnClickListener that calls back to a method defined by your app's code:

Котлин

val action = Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(::onClickNavigate)
    .build()

Ява

Action action = new Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(this::onClickNavigate)
    .build();

The onClickNavigate method can then start the default navigation car app by using the CarContext.startCarApp method:

Котлин

private fun onClickNavigate() {
    val intent = Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address))
    carContext.startCarApp(intent)
}

Ява

private void onClickNavigate() {
    Intent intent = new Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address));
    getCarContext().startCarApp(intent);
}

For more details on how to start apps, including the format of the ACTION_NAVIGATE intent, see the Start a car app with an intent section.

Some actions, such as those that require directing the user to continue the interaction on their mobile devices, are only allowed when the car is parked. You can use the ParkedOnlyOnClickListener to implement those actions. If the car is not parked, the host displays an indication to the user that the action is not allowed in this case. If the car is parked, the code executes normally. The following snippet shows how to use the ParkedOnlyOnClickListener to open a settings screen on the mobile device:

Котлин

val row = Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(::openSettingsOnPhone))
    .build()

Ява

Row row = new Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(this::openSettingsOnPhone))
    .build();

Display notifications

Notifications sent to the mobile device only show up on the car screen if they are extended with a CarAppExtender . Some notification attributes, such as content title, text, icon, and actions, can be set in the CarAppExtender , overriding the notification's attributes when they appear on the car screen.

The following snippet shows how to send a notification to the car screen that displays a different title than the one shown on the mobile device:

Котлин

val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build()

Ява

Notification notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        new CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build();

Notifications can affect the following parts of the user interface:

  • A heads-up notification (HUN) may be displayed to the user.
  • An entry in the notification center may be added, optionally with a badge visible in the rail.
  • For navigation apps, the notification may be displayed in the rail widget as described in Turn-by-turn notifications .

You can choose how to configure your app's notifications to affect each of those user interface elements by using the notification's priority, as described in the CarAppExtender documentation.

If NotificationCompat.Builder.setOnlyAlertOnce is called with a value of true , a high-priority notification displays as a HUN only once.

For more information on how to design your car app's notifications, see the Google Design for Driving guide about Notifications .

Show toasts

Your app can display a toast using CarToast as shown in this snippet:

Котлин

CarToast.makeText(carContext, "Hello!", CarToast.LENGTH_SHORT).show()

Ява

CarToast.makeText(getCarContext(), "Hello!", CarToast.LENGTH_SHORT).show();

Request permissions

If your app needs access to restricted data or actions—for example, location—the standard rules of Android permissions apply. To request a permission, you can use the CarContext.requestPermissions() method.

The benefit of using CarContext.requestPermissions() , as opposed to using standard Android APIs , is that you don't need to launch your own Activity to create the permissions dialog. Moreover, you can use the same code on both Android Auto and Android Automotive OS, rather than having to create platform-dependent flows.

Style the permissions dialog on Android Auto

On Android Auto, the permissions dialog for the user will appear on the phone. By default, there will be no background behind the dialog. To set a custom background, declare a car app theme in your AndroidManifest.xml file and set the carPermissionActivityLayout attribute for your car app theme.

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

Then, set the carPermissionActivityLayout attribute for your car app theme:

<resources>
  <style name="MyCarAppTheme">
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

Start a car app with an intent

You can call the CarContext.startCarApp method to perform one of the following actions:

  • Open the dialer to make a phone call.
  • Start turn-by-turn navigation to a location with the default navigation car app .
  • Start your own app with an intent.

The following example shows how to create a notification with an action that opens your app with a screen that shows the details of a parking reservation. You extend the notification instance with a content intent that contains a PendingIntent wrapping an explicit intent to your app's action:

Котлин

val notification = notificationBuilder
    ...
    .extend(
        CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(ComponentName(context, MyNotificationReceiver::class.java)),
                    0))
            .build())

Ява

Notification notification = notificationBuilder
    ...
    .extend(
        new CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    new Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(new ComponentName(context, MyNotificationReceiver.class)),
                    0))
            .build());

Your app must also declare a BroadcastReceiver that is invoked to process the intent when the user selects the action in the notification interface and invokes CarContext.startCarApp with an intent including the data URI:

Котлин

class MyNotificationReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val intentAction = intent.action
        if (ACTION_VIEW_PARKING_RESERVATION == intentAction) {
            CarContext.startCarApp(
                intent,
                Intent(Intent.ACTION_VIEW)
                    .setComponent(ComponentName(context, MyCarAppService::class.java))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)))
        }
    }
}

Ява

public class MyNotificationReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String intentAction = intent.getAction();
        if (ACTION_VIEW_PARKING_RESERVATION.equals(intentAction)) {
            CarContext.startCarApp(
                intent,
                new Intent(Intent.ACTION_VIEW)
                    .setComponent(new ComponentName(context, MyCarAppService.class))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)));
        }
    }
}

Finally, the Session.onNewIntent method in your app handles this intent by pushing the parking reservation screen on the stack, if it's not already on top:

Котлин

override fun onNewIntent(intent: Intent) {
    val screenManager = carContext.getCarService(ScreenManager::class.java)
    val uri = intent.data
    if (uri != null
        && MY_URI_SCHEME == uri.scheme
        && MY_URI_HOST == uri.schemeSpecificPart
        && ACTION_VIEW_PARKING_RESERVATION == uri.fragment
    ) {
        val top = screenManager.top
        if (top !is ParkingReservationScreen) {
            screenManager.push(ParkingReservationScreen(carContext))
        }
    }
}

Ява

@Override
public void onNewIntent(@NonNull Intent intent) {
    ScreenManager screenManager = getCarContext().getCarService(ScreenManager.class);
    Uri uri = intent.getData();
    if (uri != null
        && MY_URI_SCHEME.equals(uri.getScheme())
        && MY_URI_HOST.equals(uri.getSchemeSpecificPart())
        && ACTION_VIEW_PARKING_RESERVATION.equals(uri.getFragment())
    ) {
        Screen top = screenManager.getTop();
        if (!(top instanceof ParkingReservationScreen)) {
            screenManager.push(new ParkingReservationScreen(getCarContext()));
        }
    }
}

See the Display notifications section for more information on how to handle notifications for the car app.

Template restrictions

The host limits the number of templates to display for a given task to a maximum of five, of which the last template must be one of the following types:

Note that this limit applies to the number of templates and not the number of Screen instances in the stack. For example, if an app sends two templates while in screen A and then pushes screen B, it can now send three more templates. Alternatively, if each screen is structured to send a single template, then the app can push five screen instances onto the ScreenManager stack.

There are special cases to these restrictions: template refreshes and back and reset operations.

Template refreshes

Certain content updates are not counted toward the template limit. In general, if an app pushes a new template that is of the same type and contains the same main content as the previous template, the new template is not counted against the quota. For example, updating the toggle state of a row in a ListTemplate does not count against the quota. See the documentation of individual templates to learn more about what types of content updates can be considered a refresh.

Back operations

To enable sub-flows within a task, the host detects when an app is popping a Screen from the ScreenManager stack and updates the remaining quota based on the number of templates that the app is going backward by.

For example, if the app sends two templates while in screen A, then pushes screen B and sends two more templates, the app has one quota remaining. If the app then pops back to screen A, the host resets the quota to three, because the app has gone backward by two templates.

Note that, when popping back to a screen, an app must send a template that is of the same type as the one last sent by that screen. Sending any other template type causes an error. However, as long as the type remains the same during a back operation, an app can freely modify the contents of the template without affecting the quota.

Reset operations

Certain templates have special semantics that signify the end of a task. For example, the NavigationTemplate is a view that is expected to stay on the screen and be refreshed with new turn-by-turn instructions for the user's consumption. When it reaches one of these templates, the host resets the template quota, treating that template as if it is the first step of a new task. This allows the app to begin a new task. See the documentation of individual templates to see which ones trigger a reset on the host.

If the host receives an intent to start the app from a notification action or from the launcher, the quota is also reset. This mechanism lets an app begin a new task flow from notifications, and it holds true even if an app is already bound and in the foreground.

See the Display notifications section for more details on how to display your app's notifications in the car screen. See the Start a car app with an intent section for information on how to start your app from a notification action.

Connection API

You can determine whether your app is running on Android Auto or Android Automotive OS by using the CarConnection API to retrieve connection information at runtime.

For example, in your car app's Session , initialize a CarConnection and subscribe to LiveData updates:

Котлин

CarConnection(carContext).type.observe(this, ::onConnectionStateUpdated)

Ява

new CarConnection(getCarContext()).getType().observe(this, this::onConnectionStateUpdated);

In the observer, you can then react to changes in the connection state:

Котлин

fun onConnectionStateUpdated(connectionState: Int) {
  val message = when(connectionState) {
    CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not connected to a head unit"
    CarConnection.CONNECTION_TYPE_NATIVE -> "Connected to Android Automotive OS"
    CarConnection.CONNECTION_TYPE_PROJECTION -> "Connected to Android Auto"
    else -> "Unknown car connection type"
  }
  CarToast.makeText(carContext, message, CarToast.LENGTH_SHORT).show()
}

Ява

private void onConnectionStateUpdated(int connectionState) {
  String message;
  switch(connectionState) {
    case CarConnection.CONNECTION_TYPE_NOT_CONNECTED:
      message = "Not connected to a head unit";
      break;
    case CarConnection.CONNECTION_TYPE_NATIVE:
      message = "Connected to Android Automotive OS";
      break;
    case CarConnection.CONNECTION_TYPE_PROJECTION:
      message = "Connected to Android Auto";
      break;
    default:
      message = "Unknown car connection type";
      break;
  }
  CarToast.makeText(getCarContext(), message, CarToast.LENGTH_SHORT).show();
}

Constraints API

Different cars may allow for a different number of Item instances to be displayed to the user at a time. Use the ConstraintManager to check the content limit at runtime and set the appropriate number of items in your templates.

Start by getting a ConstraintManager from the CarContext :

Котлин

val manager = carContext.getCarService(ConstraintManager::class.java)

Ява

ConstraintManager manager = getCarContext().getCarService(ConstraintManager.class);

You can then query the retrieved ConstraintManager object for the relevant content limit. For example, to get the number of items that can be displayed in a grid, call getContentLimit with CONTENT_LIMIT_TYPE_GRID :

Котлин

val gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID)

Ява

int gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID);

Add a sign-in flow

If your app offers a signed-in experience for users, you can use templates like the SignInTemplate and LongMessageTemplate with Car App API level 2 and above to handle signing in to your app on the car's head unit.

To create a SignInTemplate , define a SignInMethod . The Car App Library currently supports the following sign-in methods:

For example, to implement a template that collects the user's password, start by creating an InputCallback to process and validate user input:

Котлин

val callback = object : InputCallback {
    override fun onInputSubmitted(text: String) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    override fun onInputTextChanged(text: String) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
}

Ява

InputCallback callback = new InputCallback() {
    @Override
    public void onInputSubmitted(@NonNull String text) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    @Override
    public void onInputTextChanged(@NonNull String text) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
};

An InputCallback is required for the InputSignInMethod Builder .

Котлин

val passwordInput = InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build()

Ява

InputSignInMethod passwordInput = new InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build();

Finally, use your new InputSignInMethod to create a SignInTemplate .

Котлин

SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build()

Ява

new SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build();

Use AccountManager

Android Automotive OS apps that have authentication must use AccountManager for the following reasons:

  • Better UX and ease of account management : Users can easily manage all their accounts from the accounts menu in the system settings, including sign-in and sign-out.
  • "Guest" experiences : Because cars are shared devices, OEMs can enable guest experiences in the vehicle, where accounts cannot be added.

Add text string variants

Different car screen sizes may show different amounts of text. With Car App API level 2 and above, you can specify multiple variants of a text string to best fit the screen. To see where text variants are accepted, look for templates and components that take a CarText .

You can add text string variants to a CarText with the CarText.Builder.addVariant() method:

Котлин

val itemTitle = CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build()

Ява

CarText itemTitle = new CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build();

You can then use this CarText —for example, as the primary text of a GridItem .

Котлин

GridItem.Builder()
    .addTitle(itemTitle)
    ...
    .build()

Ява

new GridItem.Builder()
    .addTitle(itemTitle)
    ...
    build();

Add strings in order from most to least preferred—for example, from longest to shortest. The host picks the appropriate-length string depending on the amount of space available on the car screen.

Add inline CarIcons for rows

You can add icons inline with text to enrich your app's visual appeal using CarIconSpan . See the documentation for CarIconSpan.create for more information on creating these spans. See Spantastic text styling with Spans for an overview of how text styling with spans work.

Котлин

  
val rating = SpannableString("Rating: 4.5 stars")
rating.setSpan(
    CarIconSpan.create(
        // Create a CarIcon with an image of four and a half stars
        CarIcon.Builder(...).build(),
        // Align the CarIcon to the baseline of the text
        CarIconSpan.ALIGN_BASELINE
    ),
    // The start index of the span (index of the character '4')
    8,
    // The end index of the span (index of the last 's' in "stars")
    16,
    Spanned.SPAN_INCLUSIVE_INCLUSIVE
)

val row = Row.Builder()
    ...
    .addText(rating)
    .build()
  
  

Ява

  
SpannableString rating = new SpannableString("Rating: 4.5 stars");
rating.setSpan(
        CarIconSpan.create(
                // Create a CarIcon with an image of four and a half stars
                new CarIcon.Builder(...).build(),
                // Align the CarIcon to the baseline of the text
                CarIconSpan.ALIGN_BASELINE
        ),
        // The start index of the span (index of the character '4')
        8,
        // The end index of the span (index of the last 's' in "stars")
        16,
        Spanned.SPAN_INCLUSIVE_INCLUSIVE
);
Row row = new Row.Builder()
        ...
        .addText(rating)
        .build();
  
  

Car Hardware APIs

Starting with Car App API level 3, the Car App Library has APIs that you can use to access vehicle properties and sensors.

Требования

To use the APIs with Android Auto, start by adding a dependency on androidx.car.app:app-projected to the build.gradle file for your Android Auto module. For Android Automotive OS, add a dependency on androidx.car.app:app-automotive to the build.gradle file for your Android Automotive OS module.

Additionally, in your AndroidManifest.xml file, you need to declare the relevant permissions needed to request the car data you want to use. Note that these permissions must also be granted to you by the user. You can use the same code on both Android Auto and Android Automotive OS, rather than having to create platform-dependent flows. However, the permissions needed are different.

CarInfo

This table describes the properties surfaced by the CarInfo APIs and the permissions you need to request to use them:

Методы Характеристики Android Auto Permissions Android Automotive OS Permissions Supported since Car App API level
fetchModel Make, model, year android.car.permission.CAR_INFO 3
fetchEnergyProfile EV connector types, fuel types com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_INFO 3
fetchExteriorDimensions

This data is only available on some Android Automotive OS vehicles running API 30 or higher

Exterior dimensions Н/Д android.car.permission.CAR_INFO 7
addTollListener
removeTollListener
Toll card state, toll card type 3
addEnergyLevelListener
removeEnergyLevelListener
Battery level, fuel level, fuel level low, range remaining com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_ENERGY ,
android.car.permission.CAR_ENERGY_PORTS ,
android.car.permission.READ_CAR_DISPLAY_UNITS
3
addSpeedListener
removeSpeedListener
Raw speed, display speed (shown on car's cluster display) com.google.android.gms.permission.CAR_SPEED android.car.permission.CAR_SPEED ,
android.car.permission.READ_CAR_DISPLAY_UNITS
3
addMileageListener
removeMileageListener

Warning: the getOdometerMeters method of the Mileage class is inaccurately named and returns kilometers, not meters.

Odometer distance com.google.android.gms.permission.CAR_MILEAGE This data is not available on Android Automotive OS to apps installed from the Play Store. 3

For example, to get the remaining range, instantiate a CarInfo object, then create and register an OnCarDataAvailableListener :

Котлин

val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo

val listener = OnCarDataAvailableListener<EnergyLevel> { data ->
    if (data.rangeRemainingMeters.status == CarValue.STATUS_SUCCESS) {
      val rangeRemaining = data.rangeRemainingMeters.value
    } else {
      // Handle error
    }
  }

carInfo.addEnergyLevelListener(carContext.mainExecutor, listener)

// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener)

Ява

CarInfo carInfo = getCarContext().getCarService(CarHardwareManager.class).getCarInfo();

OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
  if(data.getRangeRemainingMeters().getStatus() == CarValue.STATUS_SUCCESS) {
    float rangeRemaining = data.getRangeRemainingMeters().getValue();
  } else {
    // Handle error
  }
};

carInfo.addEnergyLevelListener(getCarContext().getMainExecutor(), listener);

// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener);

Don't assume that the data from the car is available at all times. If you get an error, check the status of the value you requested to better understand why the data you requested could not be retrieved. Refer to the reference documentation for the full CarInfo class definition.

CarSensors

The CarSensors class gives you access to the vehicle's accelerometer, gyroscope, compass, and location data. The availability of these values may depend on the OEM. The format for the data from the accelerometer, gyroscope, and compass is the same as you would get from the SensorManager API . For example, to check the vehicle's heading:

Котлин

val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors

val listener = OnCarDataAvailableListener<Compass> { data ->
    if (data.orientations.status == CarValue.STATUS_SUCCESS) {
      val orientation = data.orientations.value
    } else {
      // Data not available, handle error
    }
  }

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, carContext.mainExecutor, listener)

// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener)

Ява

CarSensors carSensors = getCarContext().getCarService(CarHardwareManager.class).getCarSensors();

OnCarDataAvailableListener<Compass> listener = (data) -> {
  if (data.getOrientations().getStatus() == CarValue.STATUS_SUCCESS) {
    List<Float> orientations = data.getOrientations().getValue();
  } else {
    // Data not available, handle error
  }
};

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, getCarContext().getMainExecutor(),
    listener);

// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener);

To access location data from the car, you also need to declare and request the android.permission.ACCESS_FINE_LOCATION permission.

Тестирование

To simulate sensor data when testing on Android Auto, refer to the Sensors and Sensor configuration sections of the Desktop Head Unit guide. To simulate sensor data when testing on Android Automotive OS, refer to the Emulate hardware state section of the Android Automotive OS emulator guide.

The CarAppService, Session and Screen lifecycles

The Session and Screen classes implement the LifecycleOwner interface. As the user interacts with the app, your Session and Screen objects' lifecycle callbacks are invoked, as described in the following diagrams.

The lifecycles of a CarAppService and a Session

Figure 1 . The Session lifecycle.

For full details, see the documentation for the Session.getLifecycle method.

The lifecycle of a Screen

Figure 2 . The Screen lifecycle.

For full details, see the documentation for the Screen.getLifecycle method.

Record from the car microphone

Using your app's CarAppService and the CarAudioRecord API, you can give your app access to the user's car microphone. Users need to give your app permission to access the car microphone. Your app can record and process the user's input within your app.

Permission to record

Before recording any audio, you must first declare the permission to record in your AndroidManifest.xml and request that the user grant it.

<manifest ...>
   ...
   <uses-permission android:name="android.permission.RECORD_AUDIO" />
   ...
</manifest>

You need to request the permission to record at runtime. See the Request permissions section for details on how to request a permission in your car app.

Record audio

After the user gives permission to record, you can record the audio and process the recording.

Котлин

val carAudioRecord = CarAudioRecord.create(carContext)
        carAudioRecord.startRecording()

        val data = ByteArray(CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE)
        while(carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording()
 

Ява

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        carAudioRecord.startRecording();

        byte[] data = new byte[CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE];
        while (carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording();
 

Audio focus

When recording from the car microphone, first acquire audio focus to ensure that any ongoing media is stopped. If you lose audio focus, stop recording.

Here is an example of how to acquire audio focus:

Котлин

 
val carAudioRecord = CarAudioRecord.create(carContext)
        
        // Take audio focus so that user's media is not recorded
        val audioAttributes = AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
            // Use the most appropriate usage type for your use case
            .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
            .build()
        
        val audioFocusRequest =
            AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                .setAudioAttributes(audioAttributes)
                .setOnAudioFocusChangeListener { state: Int ->
                    if (state == AudioManager.AUDIOFOCUS_LOSS) {
                        // Stop recording if audio focus is lost
                        carAudioRecord.stopRecording()
                    }
                }
                .build()
        
        if (carContext.getSystemService(AudioManager::class.java)
                .requestAudioFocus(audioFocusRequest)
            != AudioManager.AUDIOFOCUS_REQUEST_GRANTED
        ) {
            // Don't record if the focus isn't granted
            return
        }
        
        carAudioRecord.startRecording()
        // Process the audio and abandon the AudioFocusRequest when done

Ява

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        // Take audio focus so that user's media is not recorded
        AudioAttributes audioAttributes =
                new AudioAttributes.Builder()
                        .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                        // Use the most appropriate usage type for your use case
                        .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
                        .build();

        AudioFocusRequest audioFocusRequest =
                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                        .setAudioAttributes(audioAttributes)
                        .setOnAudioFocusChangeListener(state -> {
                            if (state == AudioManager.AUDIOFOCUS_LOSS) {
                                // Stop recording if audio focus is lost
                                carAudioRecord.stopRecording();
                            }
                        })
                        .build();

        if (getCarContext().getSystemService(AudioManager.class).requestAudioFocus(audioFocusRequest)
                != AUDIOFOCUS_REQUEST_GRANTED) {
            // Don't record if the focus isn't granted
            return;
        }

        carAudioRecord.startRecording();
        // Process the audio and abandon the AudioFocusRequest when done
 

Testing Library

The Android for Cars Testing Library provides auxiliary classes that you can use to validate your app's behavior in a test environment. For example, the SessionController lets you simulate a connection to the host and verify that the correct Screen and Template are created and returned.

Refer to the Samples for usage examples.

Report an Android for Cars App Library issue

If you find an issue with the library, report it using the Google Issue Tracker . Be sure to fill out all the requested information in the issue template.

Create a new issue

Before filing a new issue, please check whether it is listed in the library's release notes or reported in the issues list. You can subscribe and vote for issues by clicking the star for an issue in the tracker. For more information, see Subscribing to an Issue .

,

The Android for Cars App Library lets you bring your navigation , point of interest (POI) , internet of things (IOT) , or weather app to the car. It does so by providing a set of templates designed to meet driver distraction standards and taking care of details like the variety of car screen factors and input modalities.

This guide provides an overview of the library's key features and concepts and walks you through the process of setting up a basic app.

Прежде чем начать

  1. Review the Design for Driving pages covering the Car App Library
  2. Review the key terms and concepts in the following section.
  3. Familiarize yourself with the Android Auto System UI and Android Automotive OS design .
  4. Review the Release Notes .
  5. Review the Samples .

Key terms and concepts

Models and Templates
The user interface is represented by a graph of model objects that can be arranged together in different ways, as allowed by the template they belong to. Templates are a subset of the models that can act as a root in those graphs. Models include the information to be displayed to the user in the form of text and images as well as attributes to configure aspects of the visual appearance of such information—for example, text colors or image sizes. The host converts the models to views that are designed to meet driver distraction standards and takes care of details like the variety of car screen factors and input modalities.
Хозяин
The host is the backend component that implements the functionality offered by the library's APIs so your app can run in the car. The responsibilities of the host range from discovering your app and managing its lifecycle to converting your models into views and notifying your app of user interactions. On mobile devices, this host is implemented by Android Auto. On Android Automotive OS, this host is installed as a system app.
Template restrictions
Different templates enforce restrictions in the content of their models. For example, list templates have limits on the number of items that can be presented to the user. Templates also have restrictions in the way they can be connected to form the flow of a task. For example, the app can only push up to five templates to the screen stack. See Template restrictions for more details.
Screen
Screen is a class provided by the library that apps implement to manage the user interface presented to the user. A Screen has a lifecycle and provides the mechanism for the app to send the template to display when the screen is visible. Screen instances can also be pushed and popped to and from a Screen stack , which ensures they adhere to the template flow restrictions .
CarAppService
CarAppService is an abstract Service class that your app must implement and export to be discovered and managed by the host. Your app's CarAppService is responsible for validating that a host connection can be trusted using createHostValidator and subsequently providing Session instances for each connection using onCreateSession .
Session

Session is an abstract class that your app must implement and return using CarAppService.onCreateSession . It serves as the entry point to display information on the car screen. It has a lifecycle that informs the current state of your app on the car screen, such as when your app is visible or hidden.

When a Session is started, such as when the app is first launched, the host requests for the initial Screen to display using the onCreateScreen method.

Install the Car App Library

See the Jetpack library release page for instructions on how to add the library to your app.

Configure your app's manifest files

Before you can create your car app, configure your app's manifest files as follows.

Declare your CarAppService

The host connects to your app through your CarAppService implementation. You declare this service in your manifest to let the host discover and connect to your app.

You also need to declare your app's category in the <category> element of your app's intent filter. See the list of supported app categories for the values allowed for this element.

The following code snippet shows how to declare a car app service for a point of interest app in your manifest:

<application>
    ...
   <service
       ...
        android:name=".MyCarAppService"
        android:exported="true">
      <intent-filter>
        <action android:name="androidx.car.app.CarAppService"/>
        <category android:name="androidx.car.app.category.POI"/>
      </intent-filter>
    </service>

    ...
<application>

Supported app categories

Declare your app's category by adding one or more of the following category values in the intent filter when you declare your CarAppService as described in the preceding section :

  • androidx.car.app.category.NAVIGATION : an app that provides turn-by-turn navigation directions. See Build navigation apps for cars .
  • androidx.car.app.category.POI : an app that provides functionality relevant to finding points of interest such as parking spots, charging stations, and gas stations. See Build point of interest apps for cars .
  • androidx.car.app.category.IOT : an app that enables users to take relevant actions on connected devices from within the car. See Build internet of things apps for cars .
  • androidx.car.app.category.WEATHER : an app that lets users see relevant weather information related to their current location or along their route. See Build weather apps for cars .

See Android app quality for cars for detailed descriptions of each category and criteria for apps to belong to them.

Specify the app name and icon

You need to specify an app name and icon that the host can use to represent your app in the system UI.

You can specify the app name and icon that is used to represent your app using the label and icon attributes of your CarAppService :

...
<service
   android:name=".MyCarAppService"
   android:exported="true"
   android:label="@string/my_app_name"
   android:icon="@drawable/my_app_icon">
   ...
</service>
...

If the label or icon are not declared in the <service> element, the host falls back to the values specified for the <application> element.

Set a custom theme

To set a custom theme for your car app, add a <meta-data> element in your manifest file, as follows:

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

Then, declare your style resource to set the following attributes for your custom car app theme:

<resources>
  <style name="MyCarAppTheme">
    <item name="carColorPrimary">@layout/my_primary_car_color</item>
    <item name="carColorPrimaryDark">@layout/my_primary_dark_car_color</item>
    <item name="carColorSecondary">@layout/my_secondary_car_color</item>
    <item name="carColorSecondaryDark">@layout/my_secondary_dark_car_color</item>
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

Car App API level

The Car App Library defines its own API levels so that you can know which library features are supported by the template host on a vehicle. To retrieve the highest Car App API Level supported by a host, use the getCarAppApiLevel() method.

Declare the minimum Car App API Level supported by your app in your AndroidManifest.xml file:

<manifest ...>
    <application ...>
        <meta-data
            android:name="androidx.car.app.minCarApiLevel"
            android:value="1"/>
    </application>
</manifest>

See the documentation for the RequiresCarApi annotation for details on how to maintain backward compatibility and declare the minimum API level required to use a feature. For a definition of which API level is required to use a certain feature of the Car App Library, check the reference documentation for CarAppApiLevels .

Create your CarAppService and Session

Your app needs to extend the CarAppService class and implement its onCreateSession method, which returns a Session instance corresponding to the current connection to the host:

Котлин

class HelloWorldService : CarAppService() {
    ...
    override fun onCreateSession(): Session {
        return HelloWorldSession()
    }
    ...
}

Ява

public final class HelloWorldService extends CarAppService {
    ...
    @Override
    @NonNull
    public Session onCreateSession() {
        return new HelloWorldSession();
    }
    ...
}

The Session instance is responsible for returning the Screen instance to use the first time the app is started:

Котлин

class HelloWorldSession : Session() {
    ...
    override fun onCreateScreen(intent: Intent): Screen {
        return HelloWorldScreen(carContext)
    }
    ...
}

Ява

public final class HelloWorldSession extends Session {
    ...
    @Override
    @NonNull
    public Screen onCreateScreen(@NonNull Intent intent) {
        return new HelloWorldScreen(getCarContext());
    }
    ...
}

To handle scenarios where your car app needs to start from a screen that is not the home or landing screen of your app, such as handling deep links, you can pre-seed a back stack of screens using ScreenManager.push before returning from onCreateScreen . Pre-seeding allows users to navigate back to previous screens from the first screen that your app is showing.

Create your start screen

You create the screens displayed by your app by defining classes that extend the Screen class and implementing its onGetTemplate method, which returns the Template instance representing the state of the UI to display in the car screen.

The following snippet shows how to declare a Screen that uses a PaneTemplate template to display a simple “Hello world!” нить:

Котлин

class HelloWorldScreen(carContext: CarContext) : Screen(carContext) {
    override fun onGetTemplate(): Template {
        val row = Row.Builder().setTitle("Hello world!").build()
        val pane = Pane.Builder().addRow(row).build()
        return PaneTemplate.Builder(pane)
            .setHeaderAction(Action.APP_ICON)
            .build()
    }
}

Ява

public class HelloWorldScreen extends Screen {
    @NonNull
    @Override
    public Template onGetTemplate() {
        Row row = new Row.Builder().setTitle("Hello world!").build();
        Pane pane = new Pane.Builder().addRow(row).build();
        return new PaneTemplate.Builder(pane)
            .setHeaderAction(Action.APP_ICON)
            .build();
    }
}

The CarContext class

The CarContext class is a ContextWrapper subclass accessible to your Session and Screen instances. It provides access to car services, such as the ScreenManager for managing the screen stack ; the AppManager for general app-related functionality, such as accessing the Surface object for drawing maps ; and the NavigationManager used by turn-by-turn navigation apps to communicate navigation metadata and other navigation-related events with the host.

See Access the navigation templates for a comprehensive list of library functionality available to navigation apps.

CarContext also offers other functionality, such as letting you load drawable resources using the configuration from the car screen, starting an app in the car using intents, and signaling whether your app should display its map in dark theme .

Implement screen navigation

Apps often present a number of different screens, each possibly using different templates the user can navigate through as they interact with the interface displayed in the screen.

The ScreenManager class provides a screen stack you can use to push screens that can be popped automatically when the user selects a back button in the car screen or uses the hardware back button available in some cars.

The following snippet shows how to add a back action to a message template as well as an action that pushes a new screen when selected by the user:

Котлин

val template = MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener { screenManager.push(NextScreen(carContext)) }
            .build())
    .build()

Ява

MessageTemplate template = new MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        new Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener(
                () -> getScreenManager().push(new NextScreen(getCarContext())))
            .build())
    .build();

The Action.BACK object is a standard Action that automatically invokes ScreenManager.pop . This behavior can be overridden by using the OnBackPressedDispatcher instance available from the CarContext .

To help ensure the app is safe to use while driving, the screen stack can have a maximum depth of five screens. See the Template restrictions section for more details.

Refresh the contents of a template

Your app can request the content of a Screen to be invalidated by calling the Screen.invalidate method. The host subsequently calls back into your app's Screen.onGetTemplate method to retrieve the template with the new contents.

When refreshing a Screen , it is important to understand the specific content in the template that can be updated so the host does not count the new template against the template quota. See the Template restrictions section for more details.

We recommended that you structure your screens so there is a one-to-one mapping between a Screen and the type of template it returns through its onGetTemplate implementation.

Draw maps

Navigation, point of interest (POI), and weather apps using the following templates can draw maps by accessing a Surface .

To use the following templates, your app must have one of the corresponding permissions declared in a <uses-permission> element in its AndroidManifest.xml file.

Шаблон Template permission Category guidance
NavigationTemplate androidx.car.app.NAVIGATION_TEMPLATES Навигация
MapWithContentTemplate androidx.car.app.NAVIGATION_TEMPLATES OR
androidx.car.app.MAP_TEMPLATES
Navigation , POI , Weather
MapTemplate ( deprecated ) androidx.car.app.NAVIGATION_TEMPLATES Навигация
PlaceListNavigationTemplate ( deprecated ) androidx.car.app.NAVIGATION_TEMPLATES Навигация
RoutePreviewNavigationTemplate ( deprecated ) androidx.car.app.NAVIGATION_TEMPLATES Навигация

Declare the surface permission

In addition to the permission required for the template that you app is using, your app must declare the androidx.car.app.ACCESS_SURFACE permission in its AndroidManifest.xml file to get access to the surface:

<manifest ...>
  ...
  <uses-permission android:name="androidx.car.app.ACCESS_SURFACE" />
  ...
</manifest>

Access the surface

To access the Surface that the host provides, you must implement a SurfaceCallback and provide that implementation to the AppManager car service. The current Surface is passed to your SurfaceCallback in the SurfaceContainer parameter of the onSurfaceAvailable() and onSurfaceDestroyed() callbacks.

Котлин

carContext.getCarService(AppManager::class.java).setSurfaceCallback(surfaceCallback)

Ява

carContext.getCarService(AppManager.class).setSurfaceCallback(surfaceCallback);

Understand the surface's visible area

The host can draw user interface elements for the templates on top of the map. The host communicates the area of the surface that is guaranteed to be unobstructed and fully visible to the user by calling the SurfaceCallback.onVisibleAreaChanged method. Also, to minimize the number of changes, the host calls the SurfaceCallback.onStableAreaChanged method with the smallest rectangle, which is always visible based on the current template.

For example, when a navigation app uses the NavigationTemplate with an action strip on top, the action strip can hide itself when the user has not interacted with the screen for a while to make more space for the map. In this case, there is a callback to onStableAreaChanged and onVisibleAreaChanged with the same rectangle. When the action strip is hidden, only onVisibleAreaChanged is called with the larger area. If the user interacts with the screen, then again only onVisibleAreaChanged is called with the first rectangle.

Support dark theme

Apps must redraw their map onto the Surface instance with the proper dark colors when the host determines conditions warrant it, as described in Android app quality for cars .

To decide whether to draw a dark map, you can use the CarContext.isDarkMode method. Whenever the dark theme status changes, you receive a call to Session.onCarConfigurationChanged .

Let users interact with your map

When using the following templates, you can add support for users to interact with the maps you draw, such as letting them see different parts of a map by zooming and panning.

Шаблон Interactivity supported since Car App API Level
NavigationTemplate 2
PlaceListNavigationTemplate ( deprecated ) 4
RoutePreviewNavigationTemplate ( deprecated ) 4
MapTemplate ( deprecated ) 5 (introduction of template)
MapWithContentTemplate 7 (introduction of template)

Implement interactivity callbacks

The SurfaceCallback interface has several callback methods you can implement to add interactivity to maps built with the templates in the preceding section:

Взаимодействие SurfaceCallback method Supported since Car App API level
Кран onClick 5
Pinch to zoom onScale 2
Single-touch drag onScroll 2
Single-touch fling onFling 2
Double-tap onScale (with scale factor determined by template host) 2
Rotary nudge in pan mode onScroll (with distance factor determined by template host) 2

Add a map action strip

These templates can have a map action strip for map-related actions such as zooming in and out, recentering, displaying a compass, and other actions you choose to display. The map action strip can have up to four icon-only buttons that can be refreshed without impacting task depth. It hides during idle state and reappears on active state.

To receive map interactivity callbacks , you must add an Action.PAN button in the map action strip. When the user presses the pan button, the host enters pan mode, as described in the following section.

If your app omits the Action.PAN button in the map action strip, it doesn't receive user input from the SurfaceCallback methods, and the host exits any previously activated pan mode.

On a touchscreen, the pan button is not displayed.

Understand pan mode

In pan mode, the template host translates user input from non-touch input devices, such as rotary controllers and touchpads, into the appropriate SurfaceCallback methods. Respond to the user action to enter or exit pan mode with the setPanModeListener method in the NavigationTemplate.Builder . The host can hide other UI components in the template while the user is in pan mode.

Interact with the user

Your app can interact with the user using patterns similar to a mobile app.

Handle user input

Your app can respond to user input by passing the appropriate listeners to the models that support them. The following snippet shows how to create an Action model that sets an OnClickListener that calls back to a method defined by your app's code:

Котлин

val action = Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(::onClickNavigate)
    .build()

Ява

Action action = new Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(this::onClickNavigate)
    .build();

The onClickNavigate method can then start the default navigation car app by using the CarContext.startCarApp method:

Котлин

private fun onClickNavigate() {
    val intent = Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address))
    carContext.startCarApp(intent)
}

Ява

private void onClickNavigate() {
    Intent intent = new Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address));
    getCarContext().startCarApp(intent);
}

For more details on how to start apps, including the format of the ACTION_NAVIGATE intent, see the Start a car app with an intent section.

Some actions, such as those that require directing the user to continue the interaction on their mobile devices, are only allowed when the car is parked. You can use the ParkedOnlyOnClickListener to implement those actions. If the car is not parked, the host displays an indication to the user that the action is not allowed in this case. If the car is parked, the code executes normally. The following snippet shows how to use the ParkedOnlyOnClickListener to open a settings screen on the mobile device:

Котлин

val row = Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(::openSettingsOnPhone))
    .build()

Ява

Row row = new Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(this::openSettingsOnPhone))
    .build();

Display notifications

Notifications sent to the mobile device only show up on the car screen if they are extended with a CarAppExtender . Some notification attributes, such as content title, text, icon, and actions, can be set in the CarAppExtender , overriding the notification's attributes when they appear on the car screen.

The following snippet shows how to send a notification to the car screen that displays a different title than the one shown on the mobile device:

Котлин

val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build()

Ява

Notification notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        new CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build();

Notifications can affect the following parts of the user interface:

  • A heads-up notification (HUN) may be displayed to the user.
  • An entry in the notification center may be added, optionally with a badge visible in the rail.
  • For navigation apps, the notification may be displayed in the rail widget as described in Turn-by-turn notifications .

You can choose how to configure your app's notifications to affect each of those user interface elements by using the notification's priority, as described in the CarAppExtender documentation.

If NotificationCompat.Builder.setOnlyAlertOnce is called with a value of true , a high-priority notification displays as a HUN only once.

For more information on how to design your car app's notifications, see the Google Design for Driving guide about Notifications .

Show toasts

Your app can display a toast using CarToast as shown in this snippet:

Котлин

CarToast.makeText(carContext, "Hello!", CarToast.LENGTH_SHORT).show()

Ява

CarToast.makeText(getCarContext(), "Hello!", CarToast.LENGTH_SHORT).show();

Request permissions

If your app needs access to restricted data or actions—for example, location—the standard rules of Android permissions apply. To request a permission, you can use the CarContext.requestPermissions() method.

The benefit of using CarContext.requestPermissions() , as opposed to using standard Android APIs , is that you don't need to launch your own Activity to create the permissions dialog. Moreover, you can use the same code on both Android Auto and Android Automotive OS, rather than having to create platform-dependent flows.

Style the permissions dialog on Android Auto

On Android Auto, the permissions dialog for the user will appear on the phone. By default, there will be no background behind the dialog. To set a custom background, declare a car app theme in your AndroidManifest.xml file and set the carPermissionActivityLayout attribute for your car app theme.

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

Then, set the carPermissionActivityLayout attribute for your car app theme:

<resources>
  <style name="MyCarAppTheme">
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

Start a car app with an intent

You can call the CarContext.startCarApp method to perform one of the following actions:

  • Open the dialer to make a phone call.
  • Start turn-by-turn navigation to a location with the default navigation car app .
  • Start your own app with an intent.

The following example shows how to create a notification with an action that opens your app with a screen that shows the details of a parking reservation. You extend the notification instance with a content intent that contains a PendingIntent wrapping an explicit intent to your app's action:

Котлин

val notification = notificationBuilder
    ...
    .extend(
        CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(ComponentName(context, MyNotificationReceiver::class.java)),
                    0))
            .build())

Ява

Notification notification = notificationBuilder
    ...
    .extend(
        new CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    new Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(new ComponentName(context, MyNotificationReceiver.class)),
                    0))
            .build());

Your app must also declare a BroadcastReceiver that is invoked to process the intent when the user selects the action in the notification interface and invokes CarContext.startCarApp with an intent including the data URI:

Котлин

class MyNotificationReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val intentAction = intent.action
        if (ACTION_VIEW_PARKING_RESERVATION == intentAction) {
            CarContext.startCarApp(
                intent,
                Intent(Intent.ACTION_VIEW)
                    .setComponent(ComponentName(context, MyCarAppService::class.java))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)))
        }
    }
}

Ява

public class MyNotificationReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String intentAction = intent.getAction();
        if (ACTION_VIEW_PARKING_RESERVATION.equals(intentAction)) {
            CarContext.startCarApp(
                intent,
                new Intent(Intent.ACTION_VIEW)
                    .setComponent(new ComponentName(context, MyCarAppService.class))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)));
        }
    }
}

Finally, the Session.onNewIntent method in your app handles this intent by pushing the parking reservation screen on the stack, if it's not already on top:

Котлин

override fun onNewIntent(intent: Intent) {
    val screenManager = carContext.getCarService(ScreenManager::class.java)
    val uri = intent.data
    if (uri != null
        && MY_URI_SCHEME == uri.scheme
        && MY_URI_HOST == uri.schemeSpecificPart
        && ACTION_VIEW_PARKING_RESERVATION == uri.fragment
    ) {
        val top = screenManager.top
        if (top !is ParkingReservationScreen) {
            screenManager.push(ParkingReservationScreen(carContext))
        }
    }
}

Ява

@Override
public void onNewIntent(@NonNull Intent intent) {
    ScreenManager screenManager = getCarContext().getCarService(ScreenManager.class);
    Uri uri = intent.getData();
    if (uri != null
        && MY_URI_SCHEME.equals(uri.getScheme())
        && MY_URI_HOST.equals(uri.getSchemeSpecificPart())
        && ACTION_VIEW_PARKING_RESERVATION.equals(uri.getFragment())
    ) {
        Screen top = screenManager.getTop();
        if (!(top instanceof ParkingReservationScreen)) {
            screenManager.push(new ParkingReservationScreen(getCarContext()));
        }
    }
}

See the Display notifications section for more information on how to handle notifications for the car app.

Template restrictions

The host limits the number of templates to display for a given task to a maximum of five, of which the last template must be one of the following types:

Note that this limit applies to the number of templates and not the number of Screen instances in the stack. For example, if an app sends two templates while in screen A and then pushes screen B, it can now send three more templates. Alternatively, if each screen is structured to send a single template, then the app can push five screen instances onto the ScreenManager stack.

There are special cases to these restrictions: template refreshes and back and reset operations.

Template refreshes

Certain content updates are not counted toward the template limit. In general, if an app pushes a new template that is of the same type and contains the same main content as the previous template, the new template is not counted against the quota. For example, updating the toggle state of a row in a ListTemplate does not count against the quota. See the documentation of individual templates to learn more about what types of content updates can be considered a refresh.

Back operations

To enable sub-flows within a task, the host detects when an app is popping a Screen from the ScreenManager stack and updates the remaining quota based on the number of templates that the app is going backward by.

For example, if the app sends two templates while in screen A, then pushes screen B and sends two more templates, the app has one quota remaining. If the app then pops back to screen A, the host resets the quota to three, because the app has gone backward by two templates.

Note that, when popping back to a screen, an app must send a template that is of the same type as the one last sent by that screen. Sending any other template type causes an error. However, as long as the type remains the same during a back operation, an app can freely modify the contents of the template without affecting the quota.

Reset operations

Certain templates have special semantics that signify the end of a task. For example, the NavigationTemplate is a view that is expected to stay on the screen and be refreshed with new turn-by-turn instructions for the user's consumption. When it reaches one of these templates, the host resets the template quota, treating that template as if it is the first step of a new task. This allows the app to begin a new task. See the documentation of individual templates to see which ones trigger a reset on the host.

If the host receives an intent to start the app from a notification action or from the launcher, the quota is also reset. This mechanism lets an app begin a new task flow from notifications, and it holds true even if an app is already bound and in the foreground.

See the Display notifications section for more details on how to display your app's notifications in the car screen. See the Start a car app with an intent section for information on how to start your app from a notification action.

Connection API

You can determine whether your app is running on Android Auto or Android Automotive OS by using the CarConnection API to retrieve connection information at runtime.

For example, in your car app's Session , initialize a CarConnection and subscribe to LiveData updates:

Котлин

CarConnection(carContext).type.observe(this, ::onConnectionStateUpdated)

Ява

new CarConnection(getCarContext()).getType().observe(this, this::onConnectionStateUpdated);

In the observer, you can then react to changes in the connection state:

Котлин

fun onConnectionStateUpdated(connectionState: Int) {
  val message = when(connectionState) {
    CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not connected to a head unit"
    CarConnection.CONNECTION_TYPE_NATIVE -> "Connected to Android Automotive OS"
    CarConnection.CONNECTION_TYPE_PROJECTION -> "Connected to Android Auto"
    else -> "Unknown car connection type"
  }
  CarToast.makeText(carContext, message, CarToast.LENGTH_SHORT).show()
}

Ява

private void onConnectionStateUpdated(int connectionState) {
  String message;
  switch(connectionState) {
    case CarConnection.CONNECTION_TYPE_NOT_CONNECTED:
      message = "Not connected to a head unit";
      break;
    case CarConnection.CONNECTION_TYPE_NATIVE:
      message = "Connected to Android Automotive OS";
      break;
    case CarConnection.CONNECTION_TYPE_PROJECTION:
      message = "Connected to Android Auto";
      break;
    default:
      message = "Unknown car connection type";
      break;
  }
  CarToast.makeText(getCarContext(), message, CarToast.LENGTH_SHORT).show();
}

Constraints API

Different cars may allow for a different number of Item instances to be displayed to the user at a time. Use the ConstraintManager to check the content limit at runtime and set the appropriate number of items in your templates.

Start by getting a ConstraintManager from the CarContext :

Котлин

val manager = carContext.getCarService(ConstraintManager::class.java)

Ява

ConstraintManager manager = getCarContext().getCarService(ConstraintManager.class);

You can then query the retrieved ConstraintManager object for the relevant content limit. For example, to get the number of items that can be displayed in a grid, call getContentLimit with CONTENT_LIMIT_TYPE_GRID :

Котлин

val gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID)

Ява

int gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID);

Add a sign-in flow

If your app offers a signed-in experience for users, you can use templates like the SignInTemplate and LongMessageTemplate with Car App API level 2 and above to handle signing in to your app on the car's head unit.

To create a SignInTemplate , define a SignInMethod . The Car App Library currently supports the following sign-in methods:

For example, to implement a template that collects the user's password, start by creating an InputCallback to process and validate user input:

Котлин

val callback = object : InputCallback {
    override fun onInputSubmitted(text: String) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    override fun onInputTextChanged(text: String) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
}

Ява

InputCallback callback = new InputCallback() {
    @Override
    public void onInputSubmitted(@NonNull String text) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    @Override
    public void onInputTextChanged(@NonNull String text) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
};

An InputCallback is required for the InputSignInMethod Builder .

Котлин

val passwordInput = InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build()

Ява

InputSignInMethod passwordInput = new InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build();

Finally, use your new InputSignInMethod to create a SignInTemplate .

Котлин

SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build()

Ява

new SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build();

Use AccountManager

Android Automotive OS apps that have authentication must use AccountManager for the following reasons:

  • Better UX and ease of account management : Users can easily manage all their accounts from the accounts menu in the system settings, including sign-in and sign-out.
  • "Guest" experiences : Because cars are shared devices, OEMs can enable guest experiences in the vehicle, where accounts cannot be added.

Add text string variants

Different car screen sizes may show different amounts of text. With Car App API level 2 and above, you can specify multiple variants of a text string to best fit the screen. To see where text variants are accepted, look for templates and components that take a CarText .

You can add text string variants to a CarText with the CarText.Builder.addVariant() method:

Котлин

val itemTitle = CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build()

Ява

CarText itemTitle = new CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build();

You can then use this CarText —for example, as the primary text of a GridItem .

Котлин

GridItem.Builder()
    .addTitle(itemTitle)
    ...
    .build()

Ява

new GridItem.Builder()
    .addTitle(itemTitle)
    ...
    build();

Add strings in order from most to least preferred—for example, from longest to shortest. The host picks the appropriate-length string depending on the amount of space available on the car screen.

Add inline CarIcons for rows

You can add icons inline with text to enrich your app's visual appeal using CarIconSpan . See the documentation for CarIconSpan.create for more information on creating these spans. See Spantastic text styling with Spans for an overview of how text styling with spans work.

Котлин

  
val rating = SpannableString("Rating: 4.5 stars")
rating.setSpan(
    CarIconSpan.create(
        // Create a CarIcon with an image of four and a half stars
        CarIcon.Builder(...).build(),
        // Align the CarIcon to the baseline of the text
        CarIconSpan.ALIGN_BASELINE
    ),
    // The start index of the span (index of the character '4')
    8,
    // The end index of the span (index of the last 's' in "stars")
    16,
    Spanned.SPAN_INCLUSIVE_INCLUSIVE
)

val row = Row.Builder()
    ...
    .addText(rating)
    .build()
  
  

Ява

  
SpannableString rating = new SpannableString("Rating: 4.5 stars");
rating.setSpan(
        CarIconSpan.create(
                // Create a CarIcon with an image of four and a half stars
                new CarIcon.Builder(...).build(),
                // Align the CarIcon to the baseline of the text
                CarIconSpan.ALIGN_BASELINE
        ),
        // The start index of the span (index of the character '4')
        8,
        // The end index of the span (index of the last 's' in "stars")
        16,
        Spanned.SPAN_INCLUSIVE_INCLUSIVE
);
Row row = new Row.Builder()
        ...
        .addText(rating)
        .build();
  
  

Car Hardware APIs

Starting with Car App API level 3, the Car App Library has APIs that you can use to access vehicle properties and sensors.

Требования

To use the APIs with Android Auto, start by adding a dependency on androidx.car.app:app-projected to the build.gradle file for your Android Auto module. For Android Automotive OS, add a dependency on androidx.car.app:app-automotive to the build.gradle file for your Android Automotive OS module.

Additionally, in your AndroidManifest.xml file, you need to declare the relevant permissions needed to request the car data you want to use. Note that these permissions must also be granted to you by the user. You can use the same code on both Android Auto and Android Automotive OS, rather than having to create platform-dependent flows. However, the permissions needed are different.

CarInfo

This table describes the properties surfaced by the CarInfo APIs and the permissions you need to request to use them:

Методы Характеристики Android Auto Permissions Android Automotive OS Permissions Supported since Car App API level
fetchModel Make, model, year android.car.permission.CAR_INFO 3
fetchEnergyProfile EV connector types, fuel types com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_INFO 3
fetchExteriorDimensions

This data is only available on some Android Automotive OS vehicles running API 30 or higher

Exterior dimensions Н/Д android.car.permission.CAR_INFO 7
addTollListener
removeTollListener
Toll card state, toll card type 3
addEnergyLevelListener
removeEnergyLevelListener
Battery level, fuel level, fuel level low, range remaining com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_ENERGY ,
android.car.permission.CAR_ENERGY_PORTS ,
android.car.permission.READ_CAR_DISPLAY_UNITS
3
addSpeedListener
removeSpeedListener
Raw speed, display speed (shown on car's cluster display) com.google.android.gms.permission.CAR_SPEED android.car.permission.CAR_SPEED ,
android.car.permission.READ_CAR_DISPLAY_UNITS
3
addMileageListener
removeMileageListener

Warning: the getOdometerMeters method of the Mileage class is inaccurately named and returns kilometers, not meters.

Odometer distance com.google.android.gms.permission.CAR_MILEAGE This data is not available on Android Automotive OS to apps installed from the Play Store. 3

For example, to get the remaining range, instantiate a CarInfo object, then create and register an OnCarDataAvailableListener :

Котлин

val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo

val listener = OnCarDataAvailableListener<EnergyLevel> { data ->
    if (data.rangeRemainingMeters.status == CarValue.STATUS_SUCCESS) {
      val rangeRemaining = data.rangeRemainingMeters.value
    } else {
      // Handle error
    }
  }

carInfo.addEnergyLevelListener(carContext.mainExecutor, listener)

// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener)

Ява

CarInfo carInfo = getCarContext().getCarService(CarHardwareManager.class).getCarInfo();

OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
  if(data.getRangeRemainingMeters().getStatus() == CarValue.STATUS_SUCCESS) {
    float rangeRemaining = data.getRangeRemainingMeters().getValue();
  } else {
    // Handle error
  }
};

carInfo.addEnergyLevelListener(getCarContext().getMainExecutor(), listener);

// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener);

Don't assume that the data from the car is available at all times. If you get an error, check the status of the value you requested to better understand why the data you requested could not be retrieved. Refer to the reference documentation for the full CarInfo class definition.

CarSensors

The CarSensors class gives you access to the vehicle's accelerometer, gyroscope, compass, and location data. The availability of these values may depend on the OEM. The format for the data from the accelerometer, gyroscope, and compass is the same as you would get from the SensorManager API . For example, to check the vehicle's heading:

Котлин

val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors

val listener = OnCarDataAvailableListener<Compass> { data ->
    if (data.orientations.status == CarValue.STATUS_SUCCESS) {
      val orientation = data.orientations.value
    } else {
      // Data not available, handle error
    }
  }

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, carContext.mainExecutor, listener)

// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener)

Ява

CarSensors carSensors = getCarContext().getCarService(CarHardwareManager.class).getCarSensors();

OnCarDataAvailableListener<Compass> listener = (data) -> {
  if (data.getOrientations().getStatus() == CarValue.STATUS_SUCCESS) {
    List<Float> orientations = data.getOrientations().getValue();
  } else {
    // Data not available, handle error
  }
};

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, getCarContext().getMainExecutor(),
    listener);

// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener);

To access location data from the car, you also need to declare and request the android.permission.ACCESS_FINE_LOCATION permission.

Тестирование

To simulate sensor data when testing on Android Auto, refer to the Sensors and Sensor configuration sections of the Desktop Head Unit guide. To simulate sensor data when testing on Android Automotive OS, refer to the Emulate hardware state section of the Android Automotive OS emulator guide.

The CarAppService, Session and Screen lifecycles

The Session and Screen classes implement the LifecycleOwner interface. As the user interacts with the app, your Session and Screen objects' lifecycle callbacks are invoked, as described in the following diagrams.

The lifecycles of a CarAppService and a Session

Figure 1 . The Session lifecycle.

For full details, see the documentation for the Session.getLifecycle method.

The lifecycle of a Screen

Figure 2 . The Screen lifecycle.

For full details, see the documentation for the Screen.getLifecycle method.

Record from the car microphone

Using your app's CarAppService and the CarAudioRecord API, you can give your app access to the user's car microphone. Users need to give your app permission to access the car microphone. Your app can record and process the user's input within your app.

Permission to record

Before recording any audio, you must first declare the permission to record in your AndroidManifest.xml and request that the user grant it.

<manifest ...>
   ...
   <uses-permission android:name="android.permission.RECORD_AUDIO" />
   ...
</manifest>

You need to request the permission to record at runtime. See the Request permissions section for details on how to request a permission in your car app.

Record audio

After the user gives permission to record, you can record the audio and process the recording.

Котлин

val carAudioRecord = CarAudioRecord.create(carContext)
        carAudioRecord.startRecording()

        val data = ByteArray(CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE)
        while(carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording()
 

Ява

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        carAudioRecord.startRecording();

        byte[] data = new byte[CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE];
        while (carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording();
 

Audio focus

When recording from the car microphone, first acquire audio focus to ensure that any ongoing media is stopped. If you lose audio focus, stop recording.

Here is an example of how to acquire audio focus:

Котлин

 
val carAudioRecord = CarAudioRecord.create(carContext)
        
        // Take audio focus so that user's media is not recorded
        val audioAttributes = AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
            // Use the most appropriate usage type for your use case
            .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
            .build()
        
        val audioFocusRequest =
            AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                .setAudioAttributes(audioAttributes)
                .setOnAudioFocusChangeListener { state: Int ->
                    if (state == AudioManager.AUDIOFOCUS_LOSS) {
                        // Stop recording if audio focus is lost
                        carAudioRecord.stopRecording()
                    }
                }
                .build()
        
        if (carContext.getSystemService(AudioManager::class.java)
                .requestAudioFocus(audioFocusRequest)
            != AudioManager.AUDIOFOCUS_REQUEST_GRANTED
        ) {
            // Don't record if the focus isn't granted
            return
        }
        
        carAudioRecord.startRecording()
        // Process the audio and abandon the AudioFocusRequest when done

Ява

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        // Take audio focus so that user's media is not recorded
        AudioAttributes audioAttributes =
                new AudioAttributes.Builder()
                        .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                        // Use the most appropriate usage type for your use case
                        .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
                        .build();

        AudioFocusRequest audioFocusRequest =
                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                        .setAudioAttributes(audioAttributes)
                        .setOnAudioFocusChangeListener(state -> {
                            if (state == AudioManager.AUDIOFOCUS_LOSS) {
                                // Stop recording if audio focus is lost
                                carAudioRecord.stopRecording();
                            }
                        })
                        .build();

        if (getCarContext().getSystemService(AudioManager.class).requestAudioFocus(audioFocusRequest)
                != AUDIOFOCUS_REQUEST_GRANTED) {
            // Don't record if the focus isn't granted
            return;
        }

        carAudioRecord.startRecording();
        // Process the audio and abandon the AudioFocusRequest when done
 

Testing Library

The Android for Cars Testing Library provides auxiliary classes that you can use to validate your app's behavior in a test environment. For example, the SessionController lets you simulate a connection to the host and verify that the correct Screen and Template are created and returned.

Refer to the Samples for usage examples.

Report an Android for Cars App Library issue

If you find an issue with the library, report it using the Google Issue Tracker . Be sure to fill out all the requested information in the issue template.

Create a new issue

Before filing a new issue, please check whether it is listed in the library's release notes or reported in the issues list. You can subscribe and vote for issues by clicking the star for an issue in the tracker. For more information, see Subscribing to an Issue .