Оставаться на связи с помощью сообщений важно для многих водителей. Приложения чата могут сообщать пользователям, нужно ли забирать ребенка или изменилось ли место ужина. Платформа Android позволяет приложениям для обмена сообщениями расширять свои услуги в процессе вождения, используя стандартный пользовательский интерфейс, который позволяет водителям следить за дорогой.
Приложения, поддерживающие обмен сообщениями, могут расширять свои уведомления о сообщениях, чтобы позволить Android Auto использовать их во время работы Auto. Эти уведомления отображаются в режиме «Авто» и позволяют пользователям читать сообщения и отвечать на них в едином, не отвлекающем от внимания интерфейсе. А когда вы используете API MessagingStyle , вы получаете оптимизированные уведомления о сообщениях для всех устройств Android, включая Android Auto. Оптимизации включают в себя пользовательский интерфейс, предназначенный для уведомлений о сообщениях, улучшенную анимацию и поддержку встроенных изображений.
В этом руководстве показано, как расширить приложение, которое отображает сообщения пользователю и получает ответы пользователя, например приложение чата, чтобы передавать отображение сообщений и получение ответов на автоматическое устройство. Соответствующие рекомендации по проектированию см. в разделе «Приложения для обмена сообщениями» на сайте «Дизайн для вождения».
Начать
Чтобы предоставить службу обмена сообщениями для устройств Auto, ваше приложение должно заявить о поддержке Android Auto в манифесте и иметь возможность выполнять следующие действия:
- Создайте и отправьте объекты
NotificationCompat.MessagingStyle
, содержащие объекты ответа иAction
, помеченные как прочитанные. - Обработка ответа и пометка разговора как прочитанного с помощью
Service
.
Концепции и объекты
Прежде чем приступить к разработке приложения, полезно понять, как Android Auto обрабатывает сообщения.
Отдельный фрагмент сообщения называется сообщением и представлен классом MessagingStyle.Message
. Сообщение содержит отправителя, содержимое сообщения и время отправки сообщения.
Общение между пользователями называется беседой и представлено объектом MessagingStyle
. Разговор или MessagingStyle
содержит заголовок, сообщения и сведения о том, ведется ли разговор среди группы пользователей.
Чтобы уведомить пользователей об обновлениях беседы, например о новом сообщении, приложения отправляют Notification
в систему Android. Это Notification
использует объект MessagingStyle
для отображения пользовательского интерфейса, специфичного для обмена сообщениями, в тени уведомлений. Платформа Android также передает это Notification
в Android Auto, а MessagingStyle
извлекается и используется для публикации уведомления на дисплее автомобиля.
Android Auto также требует, чтобы приложения добавляли объекты Action
в Notification
, чтобы пользователь мог быстро ответить на сообщение или пометить его как прочитанное непосредственно из панели уведомлений.
Таким образом, один диалог представлен объектом Notification
, стиль которого определяется объектом MessagingStyle
. MessagingStyle
содержит все сообщения в этом диалоге в одном или нескольких объектах MessagingStyle.Message
. А чтобы соответствовать требованиям Android Auto, приложение должно прикреплять к Notification
объекты ответа и Action
как прочитанные.
Поток сообщений
В этом разделе описан типичный поток сообщений между вашим приложением и Android Auto.
- Ваше приложение получает сообщение.
- Ваше приложение генерирует уведомление
MessagingStyle
с объектами ответа и пометки какAction
. - Android Auto получает событие «новое уведомление» от системы Android и находит
MessagingStyle
,Action
ответа иAction
пометки как прочитанное. - Android Auto генерирует и отображает уведомление в автомобиле.
- Если пользователь касается уведомления на дисплее автомобиля, Android Auto запускает
Action
«Отметить как прочитанное».- В фоновом режиме ваше приложение должно обрабатывать это событие пометки как прочитанное.
- Если пользователь отвечает на уведомление с помощью голоса, Android Auto помещает расшифровку ответа пользователя в
Action
ответа, а затем запускает его.- В фоновом режиме ваше приложение должно обрабатывать это событие ответа.
Предварительные предположения
Эта страница не поможет вам создать целое приложение для обмена сообщениями. Следующий пример кода включает в себя некоторые вещи, необходимые вашему приложению, прежде чем вы начнете поддерживать обмен сообщениями с помощью Android Auto:
data class YourAppConversation(
val id: Int,
val title: String,
val recipients: MutableList<YourAppUser>,
val icon: Bitmap) {
companion object {
/** Fetches [YourAppConversation] by its [id]. */
fun getById(id: Int): YourAppConversation = // ...
}
/** Replies to this conversation with the given [message]. */
fun reply(message: String) {}
/** Marks this conversation as read. */
fun markAsRead() {}
/** Retrieves all unread messages from this conversation. */
fun getUnreadMessages(): List<YourAppMessage> { return /* ... */ }
}
data class YourAppUser(val id: Int, val name: String, val icon: Uri)
data class YourAppMessage(
val id: Int,
val sender: YourAppUser,
val body: String,
val timeReceived: Long)
Заявить о поддержке Android Auto
Когда Android Auto получает уведомление от приложения для обмена сообщениями, он проверяет, заявило ли приложение о поддержке Android Auto. Чтобы включить эту поддержку, включите следующую запись в манифест вашего приложения:
<application>
...
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc"/>
...
</application>
Эта запись манифеста относится к другому XML-файлу, который необходимо создать, по следующему пути: YourAppProject/app/src/main/res/xml/automotive_app_desc.xml
. В automotive_app_desc.xml
укажите возможности Android Auto, которые поддерживает ваше приложение. Например, чтобы объявить поддержку уведомлений, включите следующее:
<automotiveApp>
<uses name="notification" />
</automotiveApp>
Если ваше приложение можно установить в качестве обработчика SMS по умолчанию , обязательно включите следующий элемент <uses>
. Если вы этого не сделаете, для обработки входящих SMS/MMS-сообщений будет использоваться встроенный в Android Auto обработчик по умолчанию, когда ваше приложение установлено в качестве обработчика SMS по умолчанию, что может привести к дублированию уведомлений.
<automotiveApp>
...
<uses name="sms" />
</automotiveApp>
Импортируйте основную библиотеку AndroidX.
Для создания уведомлений для использования с устройствами Auto требуется основная библиотека AndroidX . Импортируйте библиотеку в свой проект следующим образом:
- В файле
build.gradle
верхнего уровня включите зависимость от репозитория Google Maven, как показано в следующем примере:
классный
allprojects { repositories { google() } }
Котлин
allprojects { repositories { google() } }
- В файл
build.gradle
вашего модуля приложения включите зависимость библиотеки AndroidX Core , как показано в следующем примере:
классный
dependencies { // If your app is written in Java implementation 'androidx.core:core:1.13.1' // If your app is written in Kotlin implementation 'androidx.core:core-ktx:1.13.1' }
Котлин
dependencies { // If your app is written in Java implementation("androidx.core:core:1.13.1") // If your app is written in Kotlin implementation("androidx.core:core-ktx:1.13.1") }
Обрабатывать действия пользователя
Вашему приложению для обмена сообщениями нужен способ обработки обновления разговора с помощью Action
. В Android Auto существует два типа объектов Action
, которые ваше приложение должно обрабатывать: ответ и пометка как прочитанные. Мы рекомендуем обрабатывать их с помощью IntentService
, который обеспечивает гибкость обработки потенциально дорогостоящих вызовов в фоновом режиме , освобождая основной поток вашего приложения.
Определить действия по намерению
Действия Intent
— это простые строки, которые определяют, для чего предназначено Intent
. Поскольку одна служба может обрабатывать несколько типов намерений, проще определить несколько строк действий, чем определять несколько компонентов IntentService
.
В примере приложения для обмена сообщениями в этом руководстве предусмотрены два обязательных типа действий: ответить и пометить как прочитанное, как показано в следующем примере кода.
private const val ACTION_REPLY = "com.example.REPLY"
private const val ACTION_MARK_AS_READ = "com.example.MARK_AS_READ"
Создать сервис
Чтобы создать службу, которая обрабатывает эти объекты Action
, вам понадобится идентификатор диалога, который представляет собой произвольную структуру данных, определенную вашим приложением и идентифицирующую диалог. Вам также понадобится клавиша удаленного ввода, которая подробно обсуждается далее в этом разделе. В следующем примере кода создается служба для выполнения необходимых действий:
private const val EXTRA_CONVERSATION_ID_KEY = "conversation_id"
private const val REMOTE_INPUT_RESULT_KEY = "reply_input"
/**
* An [IntentService] that handles reply and mark-as-read actions for
* [YourAppConversation]s.
*/
class MessagingService : IntentService("MessagingService") {
override fun onHandleIntent(intent: Intent?) {
// Fetches internal data.
val conversationId = intent!!.getIntExtra(EXTRA_CONVERSATION_ID_KEY, -1)
// Searches the database for that conversation.
val conversation = YourAppConversation.getById(conversationId)
// Handles the action that was requested in the intent. The TODOs
// are addressed in a later section.
when (intent.action) {
ACTION_REPLY -> TODO()
ACTION_MARK_AS_READ -> TODO()
}
}
}
Чтобы связать эту службу с вашим приложением, вам также необходимо зарегистрировать ее в манифесте вашего приложения, как показано в следующем примере:
<application>
<service android:name="com.example.MessagingService" />
...
</application>
Генерация и обработка намерений
Другие приложения, включая Android Auto, не могут получить Intent
, который запускает MessagingService
, поскольку Intent
передаются другим приложениям через PendingIntent
. Из-за этого ограничения вам необходимо создать объект RemoteInput
, чтобы другие приложения могли возвращать текст ответа вашему приложению, как показано в следующем примере:
/**
* Creates a [RemoteInput] that lets remote apps provide a response string
* to the underlying [Intent] within a [PendingIntent].
*/
fun createReplyRemoteInput(context: Context): RemoteInput {
// RemoteInput.Builder accepts a single parameter: the key to use to store
// the response in.
return RemoteInput.Builder(REMOTE_INPUT_RESULT_KEY).build()
// Note that the RemoteInput has no knowledge of the conversation. This is
// because the data for the RemoteInput is bound to the reply Intent using
// static methods in the RemoteInput class.
}
/** Creates an [Intent] that handles replying to the given [appConversation]. */
fun createReplyIntent(
context: Context, appConversation: YourAppConversation): Intent {
// Creates the intent backed by the MessagingService.
val intent = Intent(context, MessagingService::class.java)
// Lets the MessagingService know this is a reply request.
intent.action = ACTION_REPLY
// Provides the ID of the conversation that the reply applies to.
intent.putExtra(EXTRA_CONVERSATION_ID_KEY, appConversation.id)
return intent
}
В предложении переключателя ACTION_REPLY
в MessagingService
извлеките информацию, которая входит в Intent
ответа, как показано в следующем примере:
ACTION_REPLY -> {
// Extracts reply response from the intent using the same key that the
// RemoteInput uses.
val results: Bundle = RemoteInput.getResultsFromIntent(intent)
val message = results.getString(REMOTE_INPUT_RESULT_KEY)
// This conversation object comes from the MessagingService.
conversation.reply(message)
}
Вы обрабатываете Intent
пометить как прочитанное аналогичным образом. Однако для этого не требуется RemoteInput
, как показано в следующем примере:
/** Creates an [Intent] that handles marking the [appConversation] as read. */
fun createMarkAsReadIntent(
context: Context, appConversation: YourAppConversation): Intent {
val intent = Intent(context, MessagingService::class.java)
intent.action = ACTION_MARK_AS_READ
intent.putExtra(EXTRA_CONVERSATION_ID_KEY, appConversation.id)
return intent
}
Предложение переключателя ACTION_MARK_AS_READ
в MessagingService
не требует дополнительной логики, как показано в следующем примере:
// Marking as read has no other logic.
ACTION_MARK_AS_READ -> conversation.markAsRead()
Уведомлять пользователей о сообщениях
После завершения обработки действий диалога следующим шагом будет создание уведомлений, совместимых с Android Auto.
Создание действий
Объекты Action
можно передавать другим приложениям с помощью Notification
для запуска методов в исходном приложении. Таким образом Android Auto может пометить разговор как прочитанный или ответить на него.
Чтобы создать Action
, начните с Intent
. В следующем примере показано, как создать Intent
«ответить»:
fun createReplyAction(
context: Context, appConversation: YourAppConversation): Action {
val replyIntent: Intent = createReplyIntent(context, appConversation)
// ...
Затем оберните это Intent
в PendingIntent
, что подготовит его для использования внешним приложением. PendingIntent
блокирует весь доступ к обернутому Intent
предоставляя только выбранный набор методов, которые позволяют принимающему приложению активировать Intent
или получить имя пакета исходного приложения. Внешнее приложение никогда не сможет получить доступ к базовому Intent
или данным внутри него.
// ...
val replyPendingIntent = PendingIntent.getService(
context,
createReplyId(appConversation), // Method explained later.
replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
// ...
Прежде чем настраивать Action
ответа, имейте в виду, что Android Auto предъявляет три требования к Action
ответа:
- Семантическое действие должно быть установлено в
Action.SEMANTIC_ACTION_REPLY
. -
Action
должно указывать, что при его запуске не будет отображаться пользовательский интерфейс. -
Action
должно содержать одинRemoteInput
.
Следующий пример кода устанавливает ответное Action
, соответствующее требованиям, перечисленным выше:
// ...
val replyAction = Action.Builder(R.drawable.reply, "Reply", replyPendingIntent)
// Provides context to what firing the Action does.
.setSemanticAction(Action.SEMANTIC_ACTION_REPLY)
// The action doesn't show any UI, as required by Android Auto.
.setShowsUserInterface(false)
// Don't forget the reply RemoteInput. Android Auto will use this to
// make a system call that will add the response string into
// the reply intent so it can be extracted by the messaging app.
.addRemoteInput(createReplyRemoteInput(context))
.build()
return replyAction
}
Обработка действия «Пометить как прочитанное» аналогична, за исключением того, что RemoteInput
отсутствует. Поэтому Android Auto предъявляет два требования к Action
«Пометить как прочитанное»:
- Семантическое действие установлено на
Action.SEMANTIC_ACTION_MARK_AS_READ
. - Действие указывает на то, что при запуске не будет отображаться пользовательский интерфейс.
В следующем примере кода устанавливается Action
«Пометить как прочитанное», отвечающее этим требованиям:
fun createMarkAsReadAction(
context: Context, appConversation: YourAppConversation): Action {
val markAsReadIntent = createMarkAsReadIntent(context, appConversation)
val markAsReadPendingIntent = PendingIntent.getService(
context,
createMarkAsReadId(appConversation), // Method explained below.
markAsReadIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
val markAsReadAction = Action.Builder(
R.drawable.mark_as_read, "Mark as Read", markAsReadPendingIntent)
.setSemanticAction(Action.SEMANTIC_ACTION_MARK_AS_READ)
.setShowsUserInterface(false)
.build()
return markAsReadAction
}
При генерации ожидающих намерений используются два метода: createReplyId()
и createMarkAsReadId()
. Эти методы служат кодами запроса для каждого PendingIntent
, которые используются Android для управления существующими ожидающими намерениями. Методы create()
должны возвращать уникальные идентификаторы для каждого диалога, но повторные вызовы для одного и того же диалога должны возвращать уже сгенерированный уникальный идентификатор.
Рассмотрим пример с двумя разговорами, A и B: идентификатор ответа разговора A равен 100, а его идентификатор пометки как прочитанного — 101. Идентификатор ответа разговора B — 102, а его идентификатор пометки как прочитанный — 103. Если разговор A обновляется, идентификаторы ответа и пометки как прочитанные по-прежнему равны 100 и 101. Дополнительные сведения см. в PendingIntent.FLAG_UPDATE_CURRENT
.
Создайте стиль сообщений
MessagingStyle
— это носитель информации о сообщениях, который Android Auto использует для чтения вслух каждого сообщения в разговоре.
Во-первых, пользователь устройства должен быть указан в форме объекта Person
, как показано в следующем примере:
fun createMessagingStyle(
context: Context, appConversation: YourAppConversation): MessagingStyle {
// Method defined by the messaging app.
val appDeviceUser: YourAppUser = getAppDeviceUser()
val devicePerson = Person.Builder()
// The display name (also the name that's read aloud in Android auto).
.setName(appDeviceUser.name)
// The icon to show in the notification shade in the system UI (outside
// of Android Auto).
.setIcon(appDeviceUser.icon)
// A unique key in case there are multiple people in this conversation with
// the same name.
.setKey(appDeviceUser.id)
.build()
// ...
Затем вы можете создать объект MessagingStyle
и предоставить некоторые сведения о разговоре.
// ...
val messagingStyle = MessagingStyle(devicePerson)
// Sets the conversation title. If the app's target version is lower
// than P, this will automatically mark the conversation as a group (to
// maintain backward compatibility). Use `setGroupConversation` after
// setting the conversation title to explicitly override this behavior. See
// the documentation for more information.
messagingStyle.setConversationTitle(appConversation.title)
// Group conversation means there is more than 1 recipient, so set it as such.
messagingStyle.setGroupConversation(appConversation.recipients.size > 1)
// ...
Наконец, добавьте непрочитанные сообщения.
// ...
for (appMessage in appConversation.getUnreadMessages()) {
// The sender is also represented using a Person object.
val senderPerson = Person.Builder()
.setName(appMessage.sender.name)
.setIcon(appMessage.sender.icon)
.setKey(appMessage.sender.id)
.build()
// Adds the message. More complex messages, like images,
// can be created and added by instantiating the MessagingStyle.Message
// class directly. See documentation for details.
messagingStyle.addMessage(
appMessage.body, appMessage.timeReceived, senderPerson)
}
return messagingStyle
}
Упакуйте и отправьте уведомление
После создания объектов Action
и MessagingStyle
вы можете создать и опубликовать Notification
.
fun notify(context: Context, appConversation: YourAppConversation) {
// Creates the actions and MessagingStyle.
val replyAction = createReplyAction(context, appConversation)
val markAsReadAction = createMarkAsReadAction(context, appConversation)
val messagingStyle = createMessagingStyle(context, appConversation)
// Creates the notification.
val notification = NotificationCompat.Builder(context, channel)
// A required field for the Android UI.
.setSmallIcon(R.drawable.notification_icon)
// Shows in Android Auto as the conversation image.
.setLargeIcon(appConversation.icon)
// Adds MessagingStyle.
.setStyle(messagingStyle)
// Adds reply action.
.addAction(replyAction)
// Makes the mark-as-read action invisible, so it doesn't appear
// in the Android UI but the app satisfies Android Auto's
// mark-as-read Action requirement. Both required actions can be made
// visible or invisible; it is a stylistic choice.
.addInvisibleAction(markAsReadAction)
.build()
// Posts the notification for the user to see.
val notificationManagerCompat = NotificationManagerCompat.from(context)
notificationManagerCompat.notify(appConversation.id, notification)
}
Дополнительные ресурсы
Сообщить о проблеме с Android Auto Messaging
Если вы столкнулись с проблемой при разработке приложения для обмена сообщениями для Android Auto, вы можете сообщить об этом с помощью Google Issue Tracker . Обязательно заполните всю запрашиваемую информацию в шаблоне вопроса.
Прежде чем подавать новую проблему, проверьте, есть ли о ней уже сообщение в списке проблем. Вы можете подписаться и проголосовать за проблемы, щелкнув звездочку проблемы в трекере. Дополнительную информацию см. в разделе «Подписка на выпуск» .