Создайте навигационное приложение

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

Объявите поддержку навигации в своем манифесте.

Ваше навигационное приложение должно объявить категорию автомобильного приложения androidx.car.app.category.NAVIGATION в фильтре намерений своего CarAppService :

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

Поддержка навигационных целей

Чтобы поддерживать намерения навигации в вашем приложении, в том числе те, которые поступают от Google Assistant с помощью голосового запроса, вашему приложению необходимо обрабатывать намерение CarContext.ACTION_NAVIGATE в своих Session.onCreateScreen и Session.onNewIntent .

Подробную информацию о формате намерения см. в документации по CarContext.startCarApp .

Доступ к шаблонам навигации

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

  • NavigationTemplate : также отображает дополнительное информационное сообщение и оценку поездки во время активной навигации.
  • MapWithContentTemplate : шаблон, который позволяет приложению отображать фрагменты карты с каким-либо содержимым (например, списком). Содержимое обычно отображается как наложение поверх фрагментов карты, при этом видимые и стабильные области карты адаптируются к содержимому.

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

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

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

Для рисования карт требуется дополнительное разрешение.

Перейдите на MapWithContentTemplate.

Начиная с уровня API автомобильного приложения 7, MapTemplate , PlaceListNavigationTemplate и RoutePreviewNavigationTemplate устарели. Устаревшие шаблоны будут по-прежнему поддерживаться, но настоятельно рекомендуется перейти на MapWithContentTemplate .

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

КартаШаблон

Котлин

// MapTemplate (deprecated)
val template = MapTemplate.Builder()
    .setPane(paneBuilder.build())
    .setActionStrip(actionStrip)
    .setHeader(header)
    .setMapController(mapController)
    .build()

// MapWithContentTemplate
val template = MapWithContentTemplate.Builder()
    .setContentTemplate(
        PaneTemplate.Builder(paneBuilder.build())
            .setHeader(header)
            .build())
    .setActionStrip(actionStrip)
    .setMapController(mapController)
    .build()

Ява

// MapTemplate (deprecated)
MapTemplate template = new MapTemplate.Builder()
    .setPane(paneBuilder.build())
    .setActionStrip(actionStrip)
    .setHeader(header)
    .setMapController(mapController)
    .build();

// MapWithContentTemplate
MapWithContentTemplate template = new MapWithContentTemplate.Builder()
    .setContentTemplate(new PaneTemplate.Builder(paneBuilder.build())
        .setHeader(header)
        build())
    .setActionStrip(actionStrip)
    .setMapController(mapController)
    .build();

PlaceListNavigationШаблон

Котлин

// PlaceListNavigationTemplate (deprecated)
val template = PlaceListNavigationTemplate.Builder()
    .setItemList(itemListBuilder.build())
    .setHeader(header)
    .setActionStrip(actionStrip)
    .setMapActionStrip(mapActionStrip)
    .build()

// MapWithContentTemplate
val template = MapWithContentTemplate.Builder()
    .setContentTemplate(
        ListTemplate.Builder()
            .setSingleList(itemListBuilder.build())
            .setHeader(header)
            .build())
    .setActionStrip(actionStrip)
    .setMapController(
        MapController.Builder()
            .setMapActionStrip(mapActionStrip)
            .build())
    .build()

Ява

// PlaceListNavigationTemplate (deprecated)
PlaceListNavigationTemplate template = new PlaceListNavigationTemplate.Builder()
    .setItemList(itemListBuilder.build())
    .setHeader(header)
    .setActionStrip(actionStrip)
    .setMapActionStrip(mapActionStrip)
    .build();

// MapWithContentTemplate
MapWithContentTemplate template = new MapWithContentTemplate.Builder()
    .setContentTemplate(new ListTemplate.Builder()
        .setSingleList(itemListBuilder.build())
        .setHeader(header)
        .build())
    .setActionStrip(actionStrip)
    .setMapController(new MapController.Builder()
        .setMapActionStrip(mapActionStrip)
        .build())
    .build();

МаршрутПредварительный просмотрНавигацияШаблон

Котлин

// RoutePreviewNavigationTemplate (deprecated)
val template = RoutePreviewNavigationTemplate.Builder()
    .setItemList(
        ItemList.Builder()
            .addItem(
                Row.Builder()
                    .setTitle(title)
                    .build())
            .build())
    .setHeader(header)
    .setNavigateAction(
        Action.Builder()
            .setTitle(actionTitle)
            .setOnClickListener { ... }
            .build())
    .setActionStrip(actionStrip)
    .setMapActionStrip(mapActionStrip)
    .build()

// MapWithContentTemplate
val template = MapWithContentTemplate.Builder()
    .setContentTemplate(
        ListTemplate.Builder()
            .setSingleList(
                ItemList.Builder()
                    .addItem(
                        Row.Builder()
                            .setTitle(title)
                            .addAction(
                                Action.Builder()
                                    .setTitle(actionTitle)
                                    .setOnClickListener { ... }
                                    .build())
                            .build())
                    .build())
            .setHeader(header)
            .build())
    .setActionStrip(actionStrip)
    .setMapController(
        MapController.Builder()
            .setMapActionStrip(mapActionStrip)
            .build())
    .build()

Ява

// RoutePreviewNavigationTemplate (deprecated)
RoutePreviewNavigationTemplate template = new RoutePreviewNavigationTemplate.Builder()
    .setItemList(new ItemList.Builder()
        .addItem(new Row.Builder()
            .setTitle(title))
            .build())
        .build())
    .setHeader(header)
    .setNavigateAction(new Action.Builder()
        .setTitle(actionTitle)
        .setOnClickListener(() -> { ... })
        .build())
    .setActionStrip(actionStrip)
    .setMapActionStrip(mapActionStrip)
    .build();

// MapWithContentTemplate
MapWithContentTemplate template = new MapWithContentTemplate.Builder()
    .setContentTemplate(new ListTemplate.Builder()
        .setSingleList(new ItemList.Builder()
            .addItem(new Row.Builder()
                  .setTitle(title))
                  .addAction(new Action.Builder()
                      .setTitle(actionTitle)
                      .setOnClickListener(() -> { ... })
                      .build())
                  .build())
            .build()))
        .setHeader(header)
        .build())
    .setActionStrip(actionStrip)
    .setMapController(new MapController.Builder()
        .setMapActionStrip(mapActionStrip)
        .build())
    .build();

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

Метаданные навигации предоставляются через автомобильный сервис NavigationManager , доступный из CarContext :

Котлин

val navigationManager = carContext.getCarService(NavigationManager::class.java)

Ява

NavigationManager navigationManager = carContext.getCarService(NavigationManager.class);

Запуск, завершение и остановка навигации

Чтобы хост мог управлять несколькими навигационными приложениями, уведомлениями о маршрутах и ​​данными кластера транспортных средств, ему необходимо знать текущее состояние навигации. Когда пользователь начинает навигацию, вызовите NavigationManager.navigationStarted . Аналогичным образом, когда навигация заканчивается — например, когда пользователь достигает пункта назначения или пользователь отменяет навигацию — вызовите NavigationManager.navigationEnded .

Вызывайте NavigationManager.navigationEnded только тогда, когда пользователь завершает навигацию. Например, если вам нужно пересчитать маршрут в середине поездки, используйте вместо этого Trip.Builder.setLoading(true) .

Иногда узлу требуется приложение, чтобы остановить навигацию, и он вызывает onStopNavigation в объекте NavigationManagerCallback , предоставленном вашим приложением через NavigationManager.setNavigationManagerCallback . Затем приложение должно прекратить выдачу информации о следующем повороте на кластерном дисплее, навигационных уведомлениях и голосовых подсказках.

Обновить информацию о поездке

Во время активной навигации вызовите NavigationManager.updateTrip . Информация, предоставленная в этом вызове, может использоваться приборной панелью автомобиля и проекционными дисплеями. В зависимости от конкретного транспортного средства, которым управляете, пользователю отображается не вся информация. Например, настольное головное устройство (DHU) отображает Step добавленный к Trip , но не отображает информацию Destination .

Рисование на дисплее кластера

Чтобы обеспечить максимальное погружение в работу пользователя, возможно, вам захочется выйти за рамки отображения основных метаданных на дисплее приборной панели автомобиля. Начиная с Car App API уровня 6, навигационные приложения имеют возможность отображать собственный контент непосредственно на дисплее кластера (в поддерживаемых автомобилях) со следующими ограничениями:

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

Объявить поддержку кластера

Чтобы сообщить главному приложению, что ваше приложение поддерживает рендеринг на дисплеях кластера, вы должны добавить элемент androidx.car.app.category.FEATURE_CLUSTER <category> в <intent-filter> вашего CarAppService , как показано в следующем фрагменте:

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

Жизненный цикл и управление состоянием

Начиная с уровня API 6, жизненный цикл автомобильного приложения остается прежним, но теперь CarAppService::onCreateSession принимает параметр типа SessionInfo , который предоставляет дополнительную информацию о создаваемом Session (а именно, тип отображения и набор поддерживаемых шаблонов).

Приложения имеют возможность либо использовать один и тот же класс Session для обработки кластера и основного дисплея, либо создавать Sessions для конкретного дисплея для настройки поведения на каждом дисплее (как показано в следующем фрагменте кода).

Котлин

override fun onCreateSession(sessionInfo: SessionInfo): Session {
  return if (sessionInfo.displayType == SessionInfo.DISPLAY_TYPE_CLUSTER) {
    ClusterSession()
  } else {
    MainDisplaySession()
  }
}

Ява

@Override
@NonNull
public Session onCreateSession(@NonNull SessionInfo sessionInfo) {
  if (sessionInfo.getDisplayType() == SessionInfo.DISPLAY_TYPE_CLUSTER) {
    return new ClusterSession();
  } else {
    return new MainDisplaySession();
  }
}

Нет никаких гарантий относительно того, когда и будет ли предоставлено отображение кластера, а также возможно, что Session кластера будет единственным Session (например, пользователь переключил основной дисплей на другое приложение, пока ваше приложение активно перемещается). «Стандартное» соглашение заключается в том, что приложение получает контроль над отображением кластера только после вызова NavigationManager::navigationStarted . Однако приложению может быть предоставлено отображение кластера при отсутствии активной навигации или вообще не предоставлено отображение кластера. Ваше приложение должно обрабатывать эти сценарии, отображая состояние простоя вашего приложения на плитках карты.

Хост создает отдельные экземпляры связывателя и CarContext для каждого Session . Это означает, что при использовании таких методов, как ScreenManager::push или Screen::invalidate , затрагивается только тот Session , из которого они вызваны. Приложения должны создавать свои собственные каналы связи между этими экземплярами, если требуется Session связь (например, с помощью широковещательных рассылок , общего синглтона или чего-то еще).

Тестирование поддержки кластера

Вы можете протестировать свою реализацию как на Android Auto, так и на ОС Android Automotive. В Android Auto это делается путем настройки головного устройства настольного компьютера на эмуляцию вторичного кластерного дисплея . Для Android Automotive OS общие образы системы для уровня API 30 и выше имитируют отображение кластера.

Настройте TravelEstimate с помощью текста или значка

Чтобы настроить оценку поездки с помощью текста, значка или того и другого, используйте методы setTripIcon или setTripText класса TravelEstimate.Builder . NavigationTemplate использует TravelEstimate для установки текста и значков рядом или вместо расчетного времени прибытия, оставшегося времени и оставшегося расстояния.

Рисунок 1. Смета поездки с пользовательским значком и текстом.

В следующем фрагменте кода используются setTripIcon и setTripText для настройки оценки поездки:

Котлин

TravelEstimate.Builder(Distance.create(...), DateTimeWithZone.create(...))
      ...
      .setTripIcon(CarIcon.Builder(...).build())
      .setTripText(CarText.create(...))
      .build()

Ява

new TravelEstimate.Builder(Distance.create(...), DateTimeWithZone.create(...))
      ...
      .setTripIcon(CarIcon.Builder(...).build())
      .setTripText(CarText.create(...))
      .build();

Предоставляйте пошаговые уведомления

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

  1. Отметьте уведомление как продолжающееся с помощью метода NotificationCompat.Builder.setOngoing .
  2. Установите категорию уведомления Notification.CATEGORY_NAVIGATION .
  3. Расширьте уведомление с помощью CarAppExtender .

Уведомление о навигации отображается в виджете железной дороги в нижней части экрана автомобиля. Если уровень важности уведомления установлен на IMPORTANCE_HIGH , оно также отображается как хедз-ап-уведомление (HUN). Если важность не установлена ​​с помощью метода CarAppExtender.Builder.setImportance , используется важность канала уведомления .

Приложение может установить PendingIntent в CarAppExtender , который отправляется в приложение, когда пользователь нажимает на HUN или виджет железной дороги.

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

В следующем фрагменте показано, как создать уведомление о навигации:

Котлин

NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    ...
    .setOnlyAlertOnce(true)
    .setOngoing(true)
    .setCategory(NotificationCompat.CATEGORY_NAVIGATION)
    .extend(
        CarAppExtender.Builder()
            .setContentTitle(carScreenTitle)
            ...
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_OPEN_APP.hashCode(),
                    Intent(ACTION_OPEN_APP).setComponent(
                        ComponentName(context, MyNotificationReceiver::class.java)),
                        0))
            .setImportance(NotificationManagerCompat.IMPORTANCE_HIGH)
            .build())
    .build()

Ява

new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    ...
    .setOnlyAlertOnce(true)
    .setOngoing(true)
    .setCategory(NotificationCompat.CATEGORY_NAVIGATION)
    .extend(
        new CarAppExtender.Builder()
            .setContentTitle(carScreenTitle)
            ...
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_OPEN_APP.hashCode(),
                    new Intent(ACTION_OPEN_APP).setComponent(
                        new ComponentName(context, MyNotificationReceiver.class)),
                        0))
            .setImportance(NotificationManagerCompat.IMPORTANCE_HIGH)
            .build())
    .build();

Регулярно обновляйте уведомление TBT при изменении расстояния, что приводит к обновлению виджета железной дороги, и отображайте уведомление только как HUN. Вы можете контролировать поведение HUN, устанавливая важность уведомления с помощью CarAppExtender.Builder.setImportance . Установка важности на IMPORTANCE_HIGH отображает HUN. Установка любого другого значения только обновит виджет железной дороги.

Обновить содержимое PlaceListNavigationTemplate.

Вы можете позволить водителям обновлять содержимое одним нажатием кнопки при просмотре списков мест, созданных с помощью PlaceListNavigationTemplate . Чтобы включить обновление списка, реализуйте метод onContentRefreshRequested интерфейса OnContentRefreshListener и используйте PlaceListNavigationTemplate.Builder.setOnContentRefreshListener , чтобы установить прослушиватель в шаблоне.

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

Котлин

PlaceListNavigationTemplate.Builder()
    ...
    .setOnContentRefreshListener {
        // Execute any desired logic
        ...
        // Then call invalidate() so onGetTemplate() is called again
        invalidate()
    }
    .build()

Ява

new PlaceListNavigationTemplate.Builder()
        ...
        .setOnContentRefreshListener(() -> {
            // Execute any desired logic
            ...
            // Then call invalidate() so onGetTemplate() is called again
            invalidate();
        })
        .build();

Кнопка обновления отображается в заголовке PlaceListNavigationTemplate только в том случае, если прослушиватель имеет значение.

Когда пользователь нажимает кнопку обновления, вызывается метод onContentRefreshRequested вашей реализации OnContentRefreshListener . В onContentRefreshRequested вызовите метод Screen.invalidate . Затем хост вызывает метод Screen.onGetTemplate вашего приложения, чтобы получить шаблон с обновленным содержимым. Дополнительные сведения об обновлении шаблонов см. в разделе Обновление содержимого шаблона . Пока следующий шаблон, возвращаемый onGetTemplate имеет тот же тип, он считается обновлением и не учитывается в квоте шаблона.

Предоставить аудио-руководство

Чтобы воспроизводить навигационные указания через динамики автомобиля, ваше приложение должно запросить аудиофокус . В рамках вашего AudioFocusRequest установите использование как AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE . Кроме того, установите усиление фокуса как AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK .

Имитировать навигацию

Чтобы проверить функциональность навигации вашего приложения при отправке его в Google Play Store, ваше приложение должно реализовать обратный вызов NavigationManagerCallback.onAutoDriveEnabled . При вызове этого обратного вызова ваше приложение должно имитировать навигацию к выбранному пункту назначения, когда пользователь начинает навигацию. Ваше приложение может выйти из этого режима всякий раз, когда жизненный цикл текущего Session достигает состояния Lifecycle.Event.ON_DESTROY .

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

adb shell dumpsys activity service CAR_APP_SERVICE_NAME AUTO_DRIVE

Это показано в следующем примере:

adb shell dumpsys activity service androidx.car.app.samples.navigation.car.NavigationCarAppService AUTO_DRIVE

Автомобильное навигационное приложение по умолчанию

В Android Auto автомобильное навигационное приложение по умолчанию соответствует последнему навигационному приложению, которое запускал пользователь. Приложение по умолчанию получает намерения навигации, когда пользователь вызывает команды навигации через Ассистента или когда другое приложение отправляет намерение начать навигацию.

Отображать контекстные навигационные оповещения

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

Alert доступно только внутри NavigationTemplate . Чтобы уведомить пользователя за пределами NavigationTemplate , рассмотрите возможность использования хедз-ап-уведомления (HUN), как описано в разделе Отображение уведомлений .

Например, используйте Alert , чтобы:

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

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

Рисунок 2. Оповещение о контекстной навигации.

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

Создать оповещение

Используйте Alert.Builder для создания экземпляра Alert :

Котлин

Alert.Builder(
        /*alertId*/ 1,
        /*title*/ CarText.create("Hello"),
        /*durationMillis*/ 5000
    )
    // The fields below are optional
    .addAction(firstAction)
    .addAction(secondAction)
    .setSubtitle(CarText.create(...))
    .setIcon(CarIcon.APP_ICON)
    .setCallback(...)
    .build()

Ява

new Alert.Builder(
        /*alertId*/ 1,
        /*title*/ CarText.create("Hello"),
        /*durationMillis*/ 5000
    )
    // The fields below are optional
    .addAction(firstAction)
    .addAction(secondAction)
    .setSubtitle(CarText.create(...))
    .setIcon(CarIcon.APP_ICON)
    .setCallback(...)
    .build();

Если вы хотите прослушивать отмену или закрытие Alert , создайте реализацию интерфейса AlertCallback . Пути вызова AlertCallback :

  • Если время Alert истекло, хост вызывает метод AlertCallback.onCancel со значением AlertCallback.REASON_TIMEOUT . Затем он вызывает метод AlertCallback.onDismiss .

  • Если драйвер нажимает одну из кнопок действия, хост вызывает Action.OnClickListener , а затем вызывает AlertCallback.onDismiss .

  • Если Alert не поддерживается, хост вызывает AlertCallback.onCancel со значением AlertCallback.REASON_NOT_SUPPORTED . Хост не вызывает AlertCallback.onDismiss , поскольку Alert не было показано.

Настройка продолжительности оповещения

Выберите продолжительность Alert , соответствующую потребностям вашего приложения. Рекомендуемая продолжительность навигационного Alert — 10 секунд. Дополнительную информацию см. в разделе «Навигационные оповещения» .

Показать оповещение

Чтобы отобразить Alert , вызовите метод AppManager.showAlert , доступный через CarContext вашего приложения.

// Show an alert
carContext.getCarService(AppManager.class).showAlert(alert)
  • Вызов showAlert с Alert , alertId которого совпадает с идентификатором Alert , отображаемого в данный момент, ничего не дает. Alert не обновляется. Чтобы обновить Alert , вы должны воссоздать его с новым alertId .
  • Вызов showAlert с Alert , alertId которого отличается от отображаемого в данный момент Alert , отклоняет отображаемое в данный момент Alert .

Отклонить оповещение

Хотя Alert автоматически закрывается из-за тайм-аута или взаимодействия с драйвером, вы также можете закрыть Alert вручную, например, если его информация устарела. Чтобы закрыть Alert , вызовите метод dismissAlert с alertId Alert .

// Dismiss the same alert
carContext.getCarService(AppManager.class).dismissAlert(alert.getId())

Вызов dismissAlert с alertId , который не соответствует отображаемому в данный момент Alert ничего не дает. Это не вызывает исключения.