Используйте библиотеку приложений Android для автомобилей
Оптимизируйте свои подборки
Сохраняйте и классифицируйте контент в соответствии со своими настройками.
Библиотека приложений Android для автомобилей позволяет перенести в автомобиль приложения для навигации, достопримечательностей (POI) и Интернета вещей (IOT). Для этого он предоставляет набор шаблонов, разработанных с учетом стандартов по отвлечению внимания водителя, а также учитывает такие детали, как разнообразие факторов экрана автомобиля и способы ввода.
В этом руководстве представлен обзор основных функций и концепций библиотеки, а также описан процесс настройки базового приложения.
Кодлаб
Изучите основы библиотеки автомобильных приложений
arrow_forward
Прежде чем начать
Просмотрите страницы «Дизайн для вождения» , посвященные библиотеке автомобильных приложений.
Пользовательский интерфейс представлен графом объектов модели, которые можно располагать различными способами в зависимости от шаблона, которому они принадлежат. Шаблоны — это подмножество моделей, которые могут выступать в качестве корня в этих графах. Модели включают информацию, которая будет отображаться пользователю в виде текста и изображений, а также атрибуты для настройки аспектов визуального вида такой информации, например цвета текста или размеры изображения. Ведущий преобразует модели в представления, разработанные с учетом стандартов по отвлечению внимания водителя, и учитывает такие детали, как разнообразие факторов экрана автомобиля и способы ввода.
Хозяин
Хост — это серверный компонент, который реализует функциональные возможности, предлагаемые 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> фильтра намерений вашего приложения. Значения, разрешенные для этого элемента, см. в списке поддерживаемых категорий приложений .
В следующем фрагменте кода показано, как объявить службу автомобильного приложения для приложения «Точки интереса» в манифесте:
Объявите категорию своего приложения, добавив одно или несколько из следующих значений категории в фильтр намерений при объявлении CarAppService , как описано в предыдущем разделе :
androidx.car.app.category.POI : приложение, предоставляющее функции, необходимые для поиска достопримечательностей, таких как места для парковки, зарядные станции и заправочные станции. Дополнительную документацию по этой категории можно найти в разделе «Создание приложений для автомобилей» .
androidx.car.app.category.IOT : приложение, которое позволяет пользователям выполнять соответствующие действия на подключенных устройствах, не выходя из автомобиля. Дополнительную документацию по этой категории можно найти в разделе «Создание приложений Интернета вещей для автомобилей» .
Вам необходимо указать имя и значок приложения, которые хост может использовать для представления вашего приложения в пользовательском интерфейсе системы.
Вы можете указать имя и значок приложения, которые будут представлять ваше приложение, используя атрибуты label и icon вашего CarAppService :
Библиотека автомобильных приложений определяет свои собственные уровни API, чтобы вы могли знать, какие функции библиотеки поддерживаются узлом шаблона на автомобиле. Чтобы получить самый высокий уровень API автомобильного приложения, поддерживаемый хостом, используйте метод getCarAppApiLevel() .
Укажите минимальный уровень API автомобильного приложения, поддерживаемый вашим приложением, в файле AndroidManifest.xml :
Подробную информацию о том, как обеспечить обратную совместимость и объявить минимальный уровень 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 также предлагает другие функции, такие как возможность загрузки доступных для рисования ресурсов с использованием конфигурации с экрана автомобиля, запуск приложения в автомобиле с использованием намерений и сигнализация о том, должно ли ваше приложение отображать свою карту в темной теме .
Реализация экранной навигации
Приложения часто представляют несколько разных экранов, каждый из которых может использовать разные шаблоны, по которым пользователь может перемещаться при взаимодействии с интерфейсом, отображаемым на экране.
Класс ScreenManager предоставляет стек экранов, который можно использовать для отправки экранов, которые могут открываться автоматически, когда пользователь выбирает кнопку «Назад» на экране автомобиля или использует аппаратную кнопку «Назад», доступную в некоторых автомобилях.
В следующем фрагменте показано, как добавить действие «Назад» в шаблон сообщения, а также действие, которое открывает новый экран при выборе пользователем:
Чтобы обеспечить безопасность использования приложения во время вождения, стек экранов может иметь максимальную глубину в пять экранов. Дополнительную информацию см. в разделе «Ограничения шаблонов» .
Обновить содержимое шаблона
Ваше приложение может запросить признание содержимого Screen недействительным, вызвав метод Screen.invalidate . Впоследствии хост вызывает метод Screen.onGetTemplate вашего приложения, чтобы получить шаблон с новым содержимым.
При обновлении Screen важно понимать конкретное содержимое шаблона, которое можно обновить, чтобы хост не учитывал новый шаблон в квоте шаблона. Дополнительную информацию см. в разделе «Ограничения шаблона» .
Мы рекомендуем вам структурировать свои экраны так, чтобы между Screen и типом шаблона, который он возвращает через реализацию onGetTemplate , было однозначное соответствие.
Рисовать карты
Приложения навигации и достопримечательности (POI), использующие следующие шаблоны, могут рисовать карты, обращаясь к Surface :
В дополнение к разрешению, необходимому для шаблона, который использует ваше приложение, ваше приложение должно объявить разрешение androidx.car.app.ACCESS_SURFACE в своем файле AndroidManifest.xml , чтобы получить доступ к поверхности:
Чтобы получить доступ к Surface , предоставляемому узлом, необходимо реализовать SurfaceCallback и предоставить эту реализацию автомобильной службе AppManager . Текущая Surface передается в ваш SurfaceCallback в параметре SurfaceContainer обратных вызовов onSurfaceAvailable() и onSurfaceDestroyed() .
Организатор может рисовать элементы пользовательского интерфейса для шаблонов поверх карты. Хост сообщает область поверхности, которая гарантированно будет беспрепятственной и полностью видимой пользователю, вызывая метод SurfaceCallback.onVisibleAreaChanged . Кроме того, чтобы минимизировать количество изменений, хост вызывает метод SurfaceCallback.onStableAreaChanged с наименьшим прямоугольником, который всегда виден на основе текущего шаблона.
Например, когда навигационное приложение использует NavigationTemplate с полосой действий вверху, полоса действий может скрываться, когда пользователь какое-то время не взаимодействовал с экраном, чтобы освободить больше места для карты. В этом случае происходит обратный вызов onStableAreaChanged и onVisibleAreaChanged с тем же прямоугольником. Когда полоса действий скрыта, вызывается только onVisibleAreaChanged с большей областью. Если пользователь взаимодействует с экраном, то снова вызывается только onVisibleAreaChanged с первым прямоугольником.
Поддержка темной темы
Приложения должны перерисовать свою карту на экземпляре Surface с использованием соответствующих темных цветов, когда хост определяет, что условия этого требуют, как описано в разделе «Качество приложений Android для автомобилей» .
Позвольте пользователям взаимодействовать с вашей картой
При использовании следующих шаблонов вы можете добавить поддержку взаимодействия пользователей с нарисованными вами картами, например позволить им видеть различные части карты путем масштабирования и панорамирования.
Шаблон
Интерактивность поддерживается начиная с уровня API автомобильного приложения.
NavigationTemplate
2
PlaceListNavigationTemplate ( устарело )
4
RoutePreviewNavigationTemplate ( устарело )
4
MapTemplate ( устарело )
5 (знакомство с шаблоном)
MapWithContentTemplate
7 (знакомство с шаблоном)
Реализация интерактивных обратных вызовов
Интерфейс SurfaceCallback имеет несколько методов обратного вызова, которые можно реализовать, чтобы добавить интерактивности к картам, созданным с помощью шаблонов из предыдущего раздела:
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();
Некоторые действия, например требующие от пользователя продолжить взаимодействие на мобильных устройствах, разрешены только тогда, когда автомобиль припаркован. Вы можете использовать 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 , переопределяя атрибуты уведомления, когда они появляются на экране автомобиля.
В следующем фрагменте показано, как отправить на экран автомобиля уведомление, на котором отображается заголовок, отличный от того, который отображается на мобильном устройстве:
Notification notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setContentTitle(titleOnThePhone)
.extend(
new CarAppExtender.Builder()
.setContentTitle(titleOnTheCar)
...
.build())
.build();
Уведомления могут влиять на следующие части пользовательского интерфейса:
Пользователю может быть отображено хед-ап-уведомление (HUN).
Можно добавить запись в центр уведомлений, при необходимости со значком, видимым на направляющей.
Для навигационных приложений уведомление может отображаться в виджете железной дороги, как описано в разделе «Уведомления о поворотах» .
Вы можете выбрать, как настроить уведомления вашего приложения, чтобы они влияли на каждый из этих элементов пользовательского интерфейса, используя приоритет уведомления, как описано в документации CarAppExtender .
Дополнительную информацию о том, как создавать уведомления для автомобильного приложения, см. в руководстве Google Design for Driving об уведомлениях .
Показать тосты
Ваше приложение может отображать всплывающее уведомление с помощью CarToast , как показано в этом фрагменте:
Преимущество использования CarContext.requestPermissions() по сравнению со стандартными API-интерфейсами Android заключается в том, что вам не нужно запускать собственное Activity для создания диалогового окна разрешений. Более того, вы можете использовать один и тот же код как в Android Auto, так и в Android Automotive OS, вместо того, чтобы создавать потоки, зависящие от платформы.
Оформление диалогового окна разрешений в Android Auto
В Android Auto на телефоне появится диалоговое окно разрешений для пользователя. По умолчанию за диалогом не будет фона. Чтобы установить собственный фон, объявите тему автомобильного приложения в файле AndroidManifest.xml и установите атрибут carPermissionActivityLayout для темы автомобильного приложения.
В следующем примере показано, как создать уведомление с действием, которое открывает ваше приложение с экраном, отображающим сведения о резервировании парковки. Вы расширяете экземпляр уведомления с помощью намерения содержимого, которое содержит PendingIntent , обертывающее явное намерение для действия вашего приложения:
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 — это представление, которое, как ожидается, останется на экране и будет обновляться новыми пошаговыми инструкциями для использования пользователем. Когда он достигает одного из этих шаблонов, хост сбрасывает квоту шаблона, рассматривая этот шаблон как первый шаг новой задачи. Это позволит приложению начать новую задачу. См. документацию по отдельным шаблонам, чтобы узнать, какие из них вызывают сброс на хосте.
Если хост получает намерение запустить приложение из действия уведомления или из средства запуска, квота также сбрасывается. Этот механизм позволяет приложению начать новый поток задач из уведомлений, и он действует, даже если приложение уже привязано и находится на переднем плане.
Вы можете определить, работает ли ваше приложение на ОС Android Auto или Android Automotive, используя API CarConnection для получения информации о подключении во время выполнения.
Например, в Session вашего автомобильного приложения инициализируйте CarConnection и подпишитесь на обновления LiveData :
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 для получения соответствующего ограничения содержимого. Например, чтобы получить количество элементов, которые могут отображаться в сетке, вызовите 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-кода, отображаемого на головном устройстве.
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.
}
};
Для InputSignInMethodBuilder требуется 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 .
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 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
Классы Session и Screen реализуют интерфейс LifecycleOwner . Когда пользователь взаимодействует с приложением, вызывает обратные вызовы на жизненный цикл вашего Session и Screen , как описано на следующих диаграммах.
Жизненные циклы караппервика и сеанс
Рисунок 1 . Жизненный цикл Session .
Для получения полной информации см. Документацию для метода Session.getLifecycle .
Жизненный цикл экрана
Рисунок 2 . Жизненный цикл Screen .
Для получения полной информации см. Документацию для метода Screen.getLifecycle .
Запись из автомобильного микрофона
Используя CarAppService вашего приложения и API CarAudioRecord , вы можете предоставить своему приложению доступ к автомобильному микрофону пользователя. Пользователи должны дать вашему приложению разрешение на доступ к автомобильному микрофону. Ваше приложение может записывать и обработать ввод пользователя в вашем приложении.
Разрешение на запись
Перед записи какого -либо аудио вы должны сначала объявить разрешение на запись в вашем AndroidManifest.xml и попросить пользователя его предоставили.
Вам нужно запросить разрешение на запись во время выполнения. См. Раздел «Разрешения на запрос» для получения подробной информации о том, как запросить разрешение в приложении вашего автомобиля.
Запись звука
После того, как пользователь дает разрешение на запись, вы можете записать аудио и обработать запись.
Котлин
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 создаются и возвращаются.
Если вы найдете проблему с библиотекой, сообщите о ней, используя The Google Tracker . Обязательно заполните всю запрашиваемую информацию в шаблоне вопроса.
Перед подачей новой проблемы, пожалуйста, проверьте, указано ли он в записях библиотеки или сообщена в списке проблем. Вы можете подписаться и проголосовать за проблемы, щелкнув звездочку проблемы в трекере. Дополнительную информацию см. в разделе «Подписка на выпуск» .
Контент и образцы кода на этой странице предоставлены по лицензиям. Java и OpenJDK – это зарегистрированные товарные знаки корпорации Oracle и ее аффилированных лиц.