page.title=Providing Messaging for Auto page.tags="auto", "car", "automotive", "messaging" page.article=true page.metaDescription=Learn how to extend your messaging app for use in Android Auto devices. page.image=auto/images/assets/icons/messaging_app_notifications.png @jd:body
DevBytes: Android Auto Messaging
Staying connected through messages is important to many drivers. Chat apps can let users know if a child need to be picked up, or if a dinner location has been changed. The Android framework enables messaging apps to extend their services into car dashboards using a standard user interface that lets drivers keep their eyes on the road.
Apps that support messaging can be extended to pass messaging notifications to Auto dashboard systems, alerting them to new messages and allowing them to respond. You can configure your messaging app to provide these services when an Android mobile device with your app installed is connected to an Auto dashboard. Once connected, your app can provide text information to users and allow them to respond. The Auto dashboard system handles displaying the notification and the interface for replies.
This lesson assumes that you have built an app that displays messages to the user and receive the user's replies, such as a chat app. It shows you how to extend your app to hand those messages off to an Auto device for display and replies.
Messaging apps do not run directly on the Android dashboard hardware. They are installed on a separate Android mobile device. When the mobile device is plugged into a dashboard, the installed messaging apps can offer services for viewing and responding to messages through the Auto user interface.
To enable your app to provide messaging services for Auto devices:
Before you start designing your app, it's helpful to understand how Auto handles messaging.
Each individual chunk of communication is a message. A message is a
short length of text, suitable for the Auto device to read aloud. In a chat app,
this might be a single message from one person to another: "Fitzy -- Jane
can't come to the ball, her youngest has the croup. :-( --Liz"
.
A conversation is a group of messages that are all grouped together in some way. Auto uses the conversation information to group the messages together when presenting them to the user. In a chat app, a conversation might be all the messages between the user and another person (for example, all the messages back and forth between Darcy and Elizabeth). Every message belongs to a conversation, even if it's the only message in that conversation. Each conversation has a conversation name. The conversation name is used by Android Auto to present the messages; it's up to your app to choose an appropriate conversation name. In a chat app, the conversation name is usually the person your user is talking to.
The v4 support library defines an {@link android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation UnreadConversation} object. This object holds all messages in a conversation which have not yet been heard by the user. To give those messages to the user, you attach that {@link android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation UnreadConversation} to a notification. However, you do not attach messages to the {@link android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation UnreadConversation} directly. Instead, you must first set up an {@link android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation.Builder UnreadConversation.Builder} object for the conversation. The messages are added to the builder, then when you are ready to send the messages, you use the builder to create the actual {@link android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation UnreadConversation} and attach the {@link android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation UnreadConversation} to the notification.
Note: When Auto presents messages to the user, it uses the notification tag and ID to determine which conversation the messages belong to. It is important to use the same tag and ID for all messages in a conversation, and to not use that tag for other conversations.
This section describes how the mobile device interacts with Auto to present messages to the user.
You configure your app manifest to indicate that it supports messaging services for Auto devices and handle message actions. This section describes what changes to make to your manifest to support messaging for Auto devices.
When a user connects a Android mobile device to a dashboard running Android, the dashboard device looks for apps that declare support for vehicle services, such as messaging. You indicate that your app supports cars capabilities using the following manifest entry:
<application> ... <meta-data android:name="com.google.android.gms.car.application" android:resource="@xml/automotive_app_desc" /> ... <application>
This manifest entry refers to a secondary xml file, where you declare what Auto capabilities your app supports. For an app that supports messaging for Auto devices, add an xml file to the {@code res/xml/} your app's development project directory as {@code automotive_app_desc.xml}, with the following content:
<automotiveApp> <uses name="notification"/> </automotiveApp>
For more information about declaring capabilities for Auto devices, see Getting Started with Auto.
Auto devices use {@link android.content.Intent} objects that indicate a user has read or replied to a message provided by your app. Your app defines intent types for reading and replying to messages and adds this information to messaging notifications for Auto devices, so that the dashboard system can notify your app when a user takes one of these actions.
You define the read action and reply action intents types for your app and the {@link android.content.BroadcastReceiver} classes that handle them in the manifest. The following code example demonstrates how to declare these intents and their associated receivers.
<application> ... <receiver android:name=".MyMessageHeardReceiver"> <intent-filter> <action android:name="com.myapp.messagingservice.MY_ACTION_MESSAGE_HEARD"/> </intent-filter> </receiver> <receiver android:name=".MyMessageReplyReceiver"> <intent-filter> <action android:name="com.myapp.messagingservice.MY_ACTION_MESSAGE_REPLY"/> </intent-filter> </receiver> ... </application>
In this example, "MyMessageReadReceiver"
and
"MyMessageReplyReceiver"
are the names of the {@link
android.content.BroadcastReceiver} subclasses you define to handle the
intents. You can choose whatever you like as the action names, but it's best
to prepend your package name to ensure that the action names are unique. For
more information about handling actions, see Handle
User Actions.
Building notifications for use with Auto devices requires classes from the v4 support library. Use the Android SDK Manager to update the Extras > Android Support Repository to version 9 or higher and the Extras > Android Support Library to version 21.0.2 or higher.
After you have updated the support libraries, import them into your Android Studio development project by adding this dependency to your build.gradle file:
dependencies { ... compile 'com.android.support:support-v4:21.0.2' }
For information about importing the support library into development projects for other development environments, see Support Library Setup.
A messaging app provides messages to a connected Auto dashboard using the notifications framework. When your messaging app has a message for a user, you build a specially configured notification that is received by the dashboard system and presented to the user. The Auto device manages the presentation on the dashboard screen and may play the message via text-to-speech. The dashboard system also handles voice interaction if the user replies to a message using verbal input.
The messaging user interface for Auto presents users with two levels of information about messages. The first level of notification tells users what conversations are available, and who they are with, but not the content of the messages. Typically, a conversation is one or more messages from another user to the Auto user.
The second level of the notification is the actual content of messages in the conversation. If a user indicates they want to hear the messages in a conversation, the Auto user interface plays the messages using text-to-speech.
This section describes how to notify Auto users that conversations are available and provide the content of messages in those conversations.
Unread conversation objects contain intents for reading and replying to a conversation. You create a {@link android.app.PendingIntent} object for each of these actions, so the Auto device can notify your app of action taken by the Auto user on a particular conversation.
The following example code demonstrates how to define a {@link android.app.PendingIntent} to let your app know if a conversation was read to the Auto user:
Intent msgHeardIntent = new Intent() .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) .setAction("com.myapp.messagingservice.MY_ACTION_MESSAGE_HEARD") .putExtra("conversation_id", thisConversationId); PendingIntent msgHeardPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), thisConversationId, msgHeardIntent, PendingIntent.FLAG_UPDATE_CURRENT);
In this example, {@code thisConversationId} is an integer that identifies the current conversation. The value of {@link android.content.Intent#setAction Intent.setAction()} is the intent filter identifier for heard messages which you defined in your app manifest, as shown in Define read and reply intent filters.
If your app supports replying to conversations, you also create a {@link android.app.PendingIntent} for each conversation to notify your app that the user has replied. The following code example shows you how to build this intent for use with a particular conversation:
Intent msgReplyIntent = new Intent() .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) .setAction("com.myapp.messagingservice.MY_ACTION_MESSAGE_REPLY") .putExtra("conversation_id", thisConversationId); PendingIntent msgReplyPendingIntent = PendingIntent.getBroadcast( getApplicationContext(), thisConversationId, msgReplyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Once again, {@code thisConversationId} is an integer that uniquely identifies this conversation, and the value you pass to {@link android.content.Intent#setAction Intent.setAction()} is the intent filter identifier you defined for replies in your app manifest.
Messaging notifications for Auto organize messages into conversations using the {@link android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation} class, that represents an unread or new portion of a conversation from a particular sender. It contains a list of messages from the sender.
You generally do not configure the {@link android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation UnreadConversation} directly. Instead, you configure an {@link android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation.Builder UnreadConversation.Builder} with the information about the conversation, as shown in the following example code.
// Build a RemoteInput for receiving voice input in a Car Notification RemoteInput remoteInput = new RemoteInput.Builder(MY_VOICE_REPLY_KEY) .setLabel(getApplicationContext().getString(R.string.notification_reply)) .build(); // Create an unread conversation object to organize a group of messages // from a particular sender. UnreadConversation.Builder unreadConvBuilder = new UnreadConversation.Builder(conversationName) .setReadPendingIntent(msgHeardPendingIntent) .setReplyAction(msgReplyPendingIntent, remoteInput);
Note: You won't actually create the {@link android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation UnreadConversation} until you are almost ready to send the message.
This conversation object includes a {@link android.app.PendingIntent}, which allows the Auto device to signal your app that the conversation has been read by the Auto user. The construction of this intent is discussed in the Creating conversation read and reply intents section.
If your app supports replying to a conversation, you must call the {@link android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation.Builder#setReplyAction UnreadConversation.Builder.setReplyAction()} method and provide a pending intent to pass that user action back to your app. The {@link android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation UnreadConversation} object you create must also include a {@link android.support.v4.app.RemoteInput} object. When the Auto user receiving this conversation speaks a reply, the remote input objects lets your app get a text version of the voice reply.
When a message arrives for a conversation, you take the following steps to dispatch it as a notification to Auto.
First, add the message to the {@link android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation.Builder UnreadConversation.Builder} for this conversation, and update its timestamp:
unreadConvBuilder.addMessage(messageString) .setLatestTimestamp(currentTimestamp);
Note: If you are sending several messages at once, add them to the {@link android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation.Builder UnreadConversation.Builder} in order, from oldest to newest.
Then create the {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} object that builds the actual notification. You need to use the pending intents you created in the previous step.
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getApplicationContext()) .setSmallIcon(smallIconResourceID) .setLargeIcon(largeIconBitmap);
smallIconResourceID
largeIconBitmap
messageString
currentTimestamp
conversationName
msgHeardPendingIntent
You'll also need to extend the {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} with the {@link android.support.v4.app.NotificationCompat.CarExtender CarExtender}. This is where you actually create the {@link android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation UnreadConversation} object using the builder you just created, and attach it to the {@link android.support.v4.app.NotificationCompat.CarExtender CarExtender}:
notificationBuilder.extend(new CarExtender() .setUnreadConversation(unreadConvBuilder.build());
Note: If you wish, you can set an override icon or color for the {@link android.support.v4.app.NotificationCompat.CarExtender CarExtender} by calling {@link android.support.v4.app.NotificationCompat.CarExtender#setLargeIcon setLargeIcon()} or {@link android.support.v4.app.NotificationCompat.CarExtender#setColor setColor()}. The override icon or color is used when the notification is handled by a car, and has no effect if the notification is handled on the Android device. This is useful if the notification's default icon or color are not suitable for the car's display.
Once you've done all this, you use your app's {@link android.support.v4.app.NotificationManagerCompat} to send the notification:
NotificationManagerCompat msgNotificationManager = NotificationManagerCompat.from(context); msgNotificationManager.notify(notificationTag, notificationId, notificationBuilder.build());
When your create and dispatch a notification for messaging, you specify intents to be triggered when the Auto user hears the message and when the user dictates a reply. Your app indicates to the Android framework that it handles these intends by registering them through its manifest, as discussed in Define read and reply intent filters.
In addition to registering these intent filters, your app must provide code to handle these actions. Your app can do this by providing a service or {@link android.content.BroadcastReceiver} objects that handle these intents.
For more information about intents, see Intents and Intent Filters.
When a user listens to a messaging conversation through the Auto user interface, the dashboard device sends a read intent based on how your app defined the messaging notification. Your app catches that intent and invokes the broadcast receiver class associated with it, or the service method set up to handle that action.
The following code example shows how to define a {@link android.content.BroadcastReceiver} class to handle a received message heard intent:
public class MyMessageHeardReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // If you set up the intent as described in // "Create conversation read and reply intents", // you can get the conversation ID by calling: int thisConversationId = intent.getIntExtra("conversation_id", -1); // Remove the notification to indicate it has been read // and update the list of unread conversations in your app. } }
Once a notification is read, your app can remove it by calling {@link android.support.v4.app.NotificationManagerCompat#cancel NotificationManagerCompat.cancel()} with the notification ID. Within your app, you should mark the messages provided in the notification as read.
Note: An alternative to this implementation is to use a service in a {@link android.app.PendingIntent}.
When a user replies to a messaging conversation through the Auto user interface, the dashboard system sends a reply intent based on how your app defined the messaging notification. Your app catches that intent and invokes the broadcast receiver class associated with it, or the service method set up to handle that action.
The following code example shows how to define a {@link android.content.BroadcastReceiver} class to handle a received message reply intent:
public class MyMessageReplyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // If you set up the intent as described in // "Create conversation read and reply intents", // you can get the conversation ID by calling: int thisConversationId = intent.getIntExtra("conversation_id", -1). } /** * Get the message text from the intent. * Note that you should call * RemoteInput.getResultsFromIntent() to process * the RemoteInput. */ private CharSequence getMessageText(Intent intent) { Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); if (remoteInput != null) { return remoteInput.getCharSequence(MY_VOICE_REPLY_KEY); } return null; } }