DEVELOPERS BLOG

Integrating BlackBerry CPaaS with Android Auto: Secured Apps for Cars

CODE SAMPLES / 01.17.18 / Jeff J.

BBM Enterprise SDK is now BlackBerry Spark Communications Platform. Learn more, and download it for free at BlackBerry.com/Spark.

Introduction

 Over the past few months, you’ve probably heard a lot about BlackBerry’s unparalleled software being rolled out into the latest fleet of connected cars and autonomous vehicles. The ‘sickest whips’ at CES featured QNX embedded software systems in conceptual designs for the future, including the Aston Martin DB11 and Range Rover Supercharged. This month, a leader in software security for the automotive industry, BlackBerry CEO John Chen delivered a keynote address at the world-famous Detroit Auto Show. Clearly, BlackBerry is paving the way for the automotive future.

How You Can Build BlackBerry Secured Apps for Cars

Using the BlackBerry CPaaS (BlackBerry Spark Communications Platform), you can easily embed secure voice, video, and messaging into your application, and extend this functionality for use on Android Auto. This is an essential service for businesses with employees out in the field, who need safer access to their work applications while on the road. Fire departments, ambulances, police, delivery services, and any number of specialized vehicular workers can all benefit from their work applications being available on their car display, and having their communications secured by BlackBerry CPaaS. Thus, this article will teach you how to expand an existing sample application for use on Android Auto, where you can send and receive messages on your Android Auto display or desktop emulator.

Prerequisites:

There are only two prerequisites required to build and demo this highly secured Android Auto application:

  1. First, build the ‘RichChat’ application that comes bundled with the free download of Spark. This will serve as the starting point for this tutorial. Check out our BlackBerry Youtube channel for a quick getting started guide for RichChat.
  2. To test the application, you need to have a car display with Android Auto, or simply install the Car Head Unit emulator on your desktop. For information on how to get the emulator set up for testing, check out the official Building Apps for Auto training documentation.

Configure the Manifest for Android Auto Support

Let’s get started! First, we need to tell the Android system that our app is now compatible with Android Auto displays. In the RichChat sample, we do this by declaring support for Android Auto messaging in our app manifest.

Declaration for Android Auto support in AndroidManifest.xml:

<application>
. . .

<!--Declaration for Android Auto-->
 <meta-data android:name="com.google.android.gms.car.application"
     android:resource="@xml/automotive_app_desc" />
. . .
</application>

The above declaration refers to an additional XML file that we will add to our project. This file will tell the Android system what kind of service we would like to provide to Auto through our application. For this example, we would simply like to provide messaging and notification services to Android Auto.

Create the file automotive_app_desc.xml in the res/xml directory of your project with the following declaration:

<automotiveApp>
    <uses name="notification"/>
</automotiveApp>

Next, we will declare our read and reply broadcast receivers in the app manifest. This will allow the Android Auto framework to recognize actions (intents objects) from our user. Things like playing back the audio for a text message and replying to a message with voice commands are handled through these receivers. For our sample, this can be done by including the following declarations in AndroidManifest.xml:

Declaration for read and reply receiver:

<application>
. . . 
<receiver android:name=".auto.MessageReadReceiver"
    android:exported="false">
    <intent-filter>
        <action android:name="com.bbm.example.richchat.util.BBMNotificationManager.ACTION_MESSAGE_READ"/>
    </intent-filter>
</receiver>
 
<receiver android:name=".auto.MessageReplyReceiver"
    android:exported="false">
    <intent-filter>
        <action android:name="com.bbm.example.richchat.util.BBMNotificationManager.ACTION_MESSAGE_REPLY"/>
    </intent-filter>
</receiver> 

. . .
</application>



This initial configuration is standard for all Android Auto applications that support messaging. You can find more information about integrating messaging services for Android Auto on the official guide.

Expand the NotificationItem interface to include the conversation’s chatID

For simplicity, we will expand the NotificationItem Interface in BBMNotificationManager.java to include a method for getting the chatID directly from a chat NotificationItem. This can be done very simply by adding ~3 lines of code as follows:

 

In BBMNotificationManager.java add a declaration for the method getChatId() in the NotificationItem interface:

public interface NotificationItem {
. . .
. . .

// Add this method to the NotificationItem interface
@override
String getChatId();

In ChatMessageNotificationItem.java, define the getChatId() method for the ChatMessageNotificationItem class (this class implements the NotificationItem interface) as follows:

@Override
public String getChatId() {return mChatMessage.chatId;}

Create the read and reply BroadcastReceivers

 Create a new directory in your project for your Android Auto read and reply receivers (the directory path should look like …/richchat/auto). Next, create two new Java classes in this new directory, one for the read receiver, and one for the reply receiver:

 

Define the newly created Read Receiver:

This class defines the behavior that occurs when a user listens to a message in Android Auto. For our application, we will mark the message as ‘read’ using the ChatMessageRead API in Spark. For more information, see the in-line comments in the code below. 

Add the following code to the newly created MessageReadReceiver class: 

public class MessageReadReceiver extends BroadcastReceiver {
    private static final String TAG = MessageReadReceiver.class.getSimpleName();
 
    private static final String CONVERSATION_ID = "conversation_id";
 
    // This observer will mark messages read when they are played to the user on Android Auto
    private Observer mMarkMessagesReadObserver;
 
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "onReceive - MessageReadReceiver");
        String conversationID = intent.getStringExtra(CONVERSATION_ID);
        markMessageAsRead(conversationID);
    }
 
    /**
     * Mark all messages in the chat as read
     */
   private void markMessageAsRead(final String chatId) {
        final ObservableValue<Chat> obsChat =  BBMEnterprise.getInstance().getBbmdsProtocol().getChat(chatId);
        mMarkMessagesReadObserver = new Observer() {
            @Override
            public void changed() {
                final Chat chat = obsChat.get();
                if (chat.exists == Existence.YES) {
                    // remove ourselves as an observer so we don't get triggered again
                    obsChat.removeObserver(this);
 
                    if (chat.numMessages == 0 || chat.lastMessage == 0) {
                        return;
                    }
                    // Ask the BBM Enterprise SDK to mark the last message in the chat  
                    // All messages older than this will be marked as read.
                    BBMEnterprise.getInstance().getBbmdsProtocol().send(
                            new ChatMessageRead(chatId, chat.lastMessage)
                    );
                }
            }
        };
        // Add the chatObserver to the chat
        obsChat.addObserver(mMarkMessagesReadObserver);
        // Run the changed method
        mMarkMessagesReadObserver.changed();
    }
}

Define the newly created Reply Receiver: 

When the user replies to a message, Android Auto will send an intent back to the application and invoke the reply receiver to handle the action. For our application, we want to send the reply to the conversation it belongs to. This is done through the ChatMessageSend API in Spark. For more information, see the in-line comments in the code below 

public class MessageReplyReceiver extends BroadcastReceiver {
 
    private static final String TAG = MessageReplyReceiver.class.getSimpleName();
 
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "onReceive - MessageReplyReceiver");
        String conversationId = intent.getStringExtra(CONVERSATION_ID);
 
        if (BBMNotificationManager.REPLY_ACTION.equals(intent.getAction())) {
            CharSequence reply = getMessageText(intent);
            if (conversationId != null) {
                                                             
// Set the priority for this message to normal
                JSONObject data = MessageHelper.setPriority(null, MessageHelper.MessagePriorityLevel.NORMAL);                            // Create a ChatMessageSendObject containing the message reply
                ChatMessageSend messageSend = new ChatMessageSend(conversationId, ChatMessageSend.Tag.Text)
                        .content(reply.toString())
                        .data(data);
                Logger.d("sendMessage: sending " + messageSend);                
                // Send the message
                BBMEnterprise.getInstance().getBbmdsProtocol().send(messageSend);
            }
        }
    }
 
    /**
     * Get the message text from the intent.
     * Note that you should call {@code RemoteInput#getResultsFromIntent(intent)} to 
     * process the RemoteInput.
     */
   private CharSequence getMessageText(Intent intent) {
        Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
        if (remoteInput != null) {
            return remoteInput.getCharSequence(BBMNotificationManager.EXTRA_VOICE_REPLY);
        }
        return null;
    }
}

Extend your app’s notifications with the CarExtender

 First, add the following constants to the BBMNotificationManager class:

public static final String SEND_MESSAGE_ACTION =
        "com.bbm.example.richchat.util.BBMNotificationManager.ACTION_SEND_MESSAGE";
public static final String READ_ACTION =
        "com.bbm.example.richchat.util.BBMNotificationManager.ACTION_MESSAGE_READ";
public static final String REPLY_ACTION =
        "com.bbm.example.richchat.util.BBMNotificationManager.ACTION_MESSAGE_REPLY";
public static final String CONVERSATION_ID = "conversation_id";
public static final String EXTRA_VOICE_REPLY = "extra_voice_reply";

Then, we’ll make BBMNotificationManager extend IntentService, so that we can handle intents to send a new notification from this class.

public final class BBMNotificationManager extends IntentService

We’ll also need to implement ‘onHandleIntent()’ so that we can reply to messages directly from our car display. Add the following code to the BBMNotificationManager class:

@Override
protected void onHandleIntent(@Nullable Intent intent) {
    // Handle intent to send a new notification.
    if (intent != null && SEND_MESSAGE_ACTION.equals(intent.getAction())) {

        String chatId = intent.getStringExtra(CONVERSATION_ID);
        String reply = intent.getStringExtra("reply");

        //Message without expiry
        JSONObject data = null;
        ChatMessageSend messageSend = new ChatMessageSend(chatId, ChatMessageSend.Tag.Text)
                .content(reply)
                .data(data);

        Logger.d("sendMessage: sending " + messageSend);
        BBMEnterprise.getInstance().getBbmdsProtocol().send(messageSend);

    }
}

Next, we’ll add two methods to create our intents for read and reply actions, to notify the android auto framework to communicate these actions back to the user when they occur.

Add the following two methods to the BBMNotificationManager:

// Creates an intent that will be triggered when a message is read.
private Intent getMessageReadIntent(String conversationId) {
    return new Intent().setAction(READ_ACTION)
            .putExtra(CONVERSATION_ID, conversationId);
}
 
// Creates an Intent that will be triggered when a voice reply is received.
private Intent getMessageReplyIntent(String conversationId) {
    return new Intent().setAction(REPLY_ACTION)
            .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
            .setAction(REPLY_ACTION)
            .putExtra(CONVERSATION_ID, conversationId);
}

Finally we will extend single message notifications that are triggered in the post() method of the BBMNotificationManager class to include support for Android Auto.

Add the following code to the post() method in BBMNotificationManager.java file:

public void post(boolean silent) {
...
...
// single Notification
final NotificationItem item = notificationMessages.first();
final String ticker = String.format(mContext.getString(R.string.notification_message_ticker_format),
        item.getTitle(),
        item.getMessage()); // Adding code to support Android Auto starts here
 
// A pending Intent for reads
PendingIntent readPendingIntent = PendingIntent.getBroadcast(mContext,
        Integer.parseInt(item.getChatId()),
        getMessageReadIntent(item.getChatId()),
        PendingIntent.FLAG_UPDATE_CURRENT);
 
// Build a RemoteInput for receiving voice input in a Car Notification
RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY).build();
 
// Build a pending intent for the reply action to trigger
PendingIntent replyIntent = PendingIntent.getBroadcast(mContext, Integer.parseInt(item.getChatId()),
        getMessageReplyIntent(item.getChatId()),
        PendingIntent.FLAG_UPDATE_CURRENT);
 
// Create the UnreadConversation and populate it with the participant name,
// and read and reply intents
NotificationCompat.CarExtender.UnreadConversation.Builder unreadConversationBuilder =
        new NotificationCompat.CarExtender.UnreadConversation.Builder(item.getTitle())
                .setLatestTimestamp(item.getTimestampInMs())
                .setReadPendingIntent(readPendingIntent)
                .setReplyAction(replyIntent, remoteInput);
 
// Add the message to the unreadConversationBuilder
unreadConversationBuilder.addMessage(item.getMessage());
 
final Bitmap avatar = null;
        PendingIntent pIntent = item.getIntent();
 
NotificationCompat.Builder singleBuilder = getBuilder()
        .setSmallIcon(item.getCustomIconResId() == -1 ? getSmallNotificationIconResourceId() : item.getCustomIconResId())
        .setLargeIcon(avatar == null ? BitmapFactory.decodeResource(mContext.getResources(), getLargeNotificationIconResourceId()) : avatar)
        .setContentTitle(item.getTitle())
        .setContentText(item.getMessage())
        .setTicker(ticker)
        .setContentIntent(pIntent)
 
        // Extend the notification to include support for Android Auto
        .extend(new NotificationCompat.CarExtender()
        .setUnreadConversation(unreadConversationBuilder.build()));
...
...}

 

And that’s it! Connect to your Android Auto display or emulator and try it out! You will now have notifications from the RichChat app read to you, and will be able to reply to messages with your voice. 

RichChat on Android Auto in action:

 

For the sake of simplicity, this sample plays the most recent message received in the chat, and marks the entire conversation as read when that message is listened to. In a production environment, the app would play back the unread messages in order, and use the ChatMessage.messageId to mark the messages in the chat up to this message as ‘read’.

 

 

Jeff J.

About Jeff J.

As a part of the Enterprise Solutions Team, I work to bring the latest BlackBerry software and security features to life on the Android platform.