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

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

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

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

  1. Ознакомьтесь со страницами Design for Driving, посвященными библиотеке автомобильных приложений.
  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, необходимого для использования той или иной функции Car App Library, см. в справочной документации по 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 Car. Текущий Surface передаётся в SurfaceCallback в параметре SurfaceContainer обратных вызовов onSurfaceAvailable() и onSurfaceDestroyed() .

Котлин

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

Ява

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

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

Помимо рендеринга непосредственно в Surface с использованием API Canvas , вы также можете рендерить представления в Surface с использованием API VirtualDisplay и Presentation , как показано в этом примере:

class HelloWorldSurfaceCallback(context: Context) : SurfaceCallback {
  lateinit var virtualDisplay: VirtualDisplay
  lateinit var presentation: Presentation

  override fun onSurfaceAvailable(surfaceContainer: SurfaceContainer) {
      virtualDisplay = context
          .getSystemService(DisplayManager::class.java)
          .createVirtualDisplay(
              VIRTUAL_DISPLAY_NAME ,
              surfaceContainer.width,
              surfaceContainer.height,
              surfaceContainer.dpi,
              surfaceContainer.surface,
              0
          )

      presentation = Presentation(context, virtualDisplay.display)

      // Instantiate the view to be used as the content view
      val view = ...

      presentation.setContentView(view)
      presentation.show()
  }

  override fun onSurfaceDestroyed(surfaceContainer: SurfaceContainer) {
    presentation.dismiss()
    // This handles releasing the Surface provided when creating the VirtualDisplay
    virtualDisplay.release()
  }
}

Используйте Compose для рендеринга на виртуальном дисплее.

Вы можете использовать ComposeView в качестве представления содержимого Presentation . Поскольку ComposeView используется вне действия, необходимо убедиться, что оно или родительское представление распространяет свойства LifecycleOwner и SavedStateRegistryOwner . Для этого используйте setViewTreeLifecycleOwner и setViewTreeSavedStateRegistryOwner .

Session уже реализует LifecycleOwner , и ваша реализация может дополнительно реализовать SavedStateRegistryOwner для обслуживания обеих ролей.

class HelloWorldSession() : Session(), SavedStateRegistryOwner { ... }

class HelloWorldSurfaceCallback(session: HelloWorldSession) : SurfaceCallback {
  ...

  override fun onSurfaceAvailable(surfaceContainer: SurfaceContainer) {
    ...
    val view = ComposeView(session.carContext)
    view.setViewTreeLifecycleOwner(session)
    view.setViewTreeSavedStateRegistryOwner(session)
    view.setContent {
      // Composable content
    }

    presentation.setContentView(view)
    presentation.show()
  }

  ...
}

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

Хост может отображать элементы пользовательского интерфейса для шаблонов поверх карты. Хост сообщает пользователю область поверхности, которая гарантированно свободна и полностью видна, вызывая метод 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 Поддерживается начиная с уровня API Car App
Кран 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 в стеке. Например, если приложение отправляет два шаблона, находясь на экране A, а затем отправляет экран B, оно теперь может отправить ещё три шаблона. В качестве альтернативы, если каждый экран структурирован для отправки одного шаблона, приложение может отправить пять экземпляров экранов в стек ScreenManager .

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

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

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

Операции по восстановлению

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

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

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

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

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

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

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

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 для проверки ограничения содержимого во время выполнения и установки соответствующего количества элементов в шаблонах.

Начнем с получения 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();

Использовать AccountManager

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

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

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

На разных экранах автомобилей может отображаться разное количество текста. С помощью 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 вы можете добавлять значки в текст, чтобы улучшить визуальную привлекательность вашего приложения. Подробнее о создании этих элементов span см. в документации по CarIconSpan.create Обзор принципов работы элементов span с текстом см. в статье «Spantastic text style with Spans».

Котлин

  
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 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, а не создавать платформу-зависимые потоки. Тем не менее, необходимые разрешения разные.

CarInfo

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

Методы Характеристики Android Auto разрешения Android Automotive OS разрешения Поддерживается со времени уровня API CAR API
fetchModel Марка, модель, год выпуска android.car.permission.CAR_INFO 3
fetchEnergyProfile Типы разъемов EV, типы топлива com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_INFO 3
fetchExteriorDimensions

Эти данные доступны только на некоторых автомобилях Android Automotive OS, работающих 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 OS для приложений, установленных из 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

Класс 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 за проблему в трекере. Для получения дополнительной информации см. Подписку на проблему .