Note: If you do not already have an Andromo account register now and start creating Android apps.

Introduction

This AirBop tutorial builds on the code that was written for the Using AirBop to Push Images for BigPictureStyle Notifications tutorial. As in the last tutorial we will be pushing an image down to our client app, but instead of getting the app to download the image, this time we will be including the image data as part of the message payload.

The main topics covered in this tutorial are:

  • How to base64 encode an image.
  • How to include a base64 encoded image as part of your AirBop message payload.
  • How to decode the base64 string into image in the client app.
  • How to use the setLargeIcon() method when creating a notification.

As brief discussion on the difference between “Send-to-Sync” and “Messages with Payload” can be found in Google’s documentation.

Message Overview

In general this is how sending the image as part of the payload will work:

  1. Convert the image into a base64 encoded string.
  2. Send a message from AirBop with that base64 string included, to the Google Cloud Messaging (GCM) servers.
  3. The GCM servers deliver that message to the app.
  4. The app receives the message and decodes the base64 string back into an image.
  5. The app then uses the image in the notification it creates.

Step 0 – Using the Code from the Previous Tutorial

This tutorial won’t go over getting the AirBop-Client sample code into Eclipse and setting up your AirBop account. For that I refer you to the last tutorial. The code written for the last tutorial will be the starting point for this tutorial, so if you are just reading this tutorial I suggest you give the previous tutorial a read (or just grab the code) and then come back.

Step 1 – Reading the Encoded Image from the JSON

As with the last tutorial we will be using custom JSON when we send our message. We will have to edit the onMessage function in the GCMIntentService class. This code is pretty straightforward and basically a duplicate of similar code written in the previous tutorial:

String large_icon = null;
...
large_icon = bundle.getString("large_icon");

The difference this time is that we will use the existence of the image_url data to determine which function to call:

if (image_url != null) {
	generateImageNotification(context, title, message, url, image_url, large_icon); 	
} else {
	generateNotification(context, title, message, url, large_icon);
}

So if we get the image_url data we will create a bigPictureStyle Notification. If we don’t we will call the generateNotification method. Both methods will get the large_icon data passed to them. The OnMessage method now looks like the following:

@Override
protected void onMessage(Context context, Intent intent) {
	
    Log.i(TAG, "Received message");
    displayMessage(context, "Message Received" );
    String message = null;
    String title = null;
    String url = null;
    String image_url = null;
    String large_icon = null;
    
    if (intent != null) {      	
    	//Check the bundle for the pay load body and title
        Bundle bundle = intent.getExtras();
 	   	if (bundle != null) {
 	   		displayMessage(context, "Message bundle: " +  bundle);
 	   		
 	   		Log.i(TAG, "Message bundle: " +  bundle);
 	   		message = bundle.getString("message");   			 	   		
 	   		title = bundle.getString("title");	
 	   		url = bundle.getString("url");	 	   		
 	   		image_url = bundle.getString("image_url");
 	   		large_icon = bundle.getString("large_icon");
 	   	} 
    }
   	// If there was no body just use a standard message
   	if (message == null) {
   		message = getString(R.string.airbop_message);
	}
   	
   	if (image_url != null) {
   		generateImageNotification(context, title, message, url, image_url, large_icon); 	
   	} else {
   		generateNotification(context, title, message, url, large_icon);
   	}
} 

Step 2 – Creating the decodeImage() Function

In this step we will create the decodeImage() function that we will call to decode the base64-encoded image string back into a bitmap. The function is quite straightforward, it accepts a string as it’s only parameters and then, if the string is valid, it will attempt to base64 decode the image data. If the data is successfully decoded then it will attempt to create a Bitmap from it and then return that Bitmap:

/**
 * Decode a base64 string into a Bitmap
 */
private static Bitmap decodeImage(String image_data) {
	// Decode the encoded string into largeIcon
	Bitmap largeIcon = null;
	if ((image_data != null) && (!image_data.equals(""))) {
		byte[] decodedImage = Base64.decode(image_data, Base64.DEFAULT);
		if (decodedImage != null) {
			largeIcon = BitmapFactory.decodeByteArray(decodedImage
					, 0
					, decodedImage.length);
		}
	}
	return largeIcon;
}

This function requires the following additional imports to be added to the class:

import android.util.Base64;
import android.graphics.BitmapFactory;

Step 3 – Updating the Notification Methods

In this step will update the generateNotification() and generateImageNotification() methods to accept our new large_icon parameter. The first step is to change the function declarations from:

private static void generateNotification(Context context
		, String title
		, String message
		, String url)

To:

private static void generateNotification(Context context
		, String title
		, String message
		, String url
		, String large_icon)

And:

private static void generateImageNotification(Context context
		, String title
		, String message
		, String url
		, String image_url)

To:

private static void generateImageNotification(Context context
		, String title
		, String message
		, String url
		, String image_url
		, String large_icon)

Now we have to call the setLargeIcon() method passing it the decoded Bitmap in both methods, when the notifications are created. In generateNotification() it will look like this:

Notification notification = new NotificationCompat.Builder(context)
		.setContentTitle(title)
		.setContentText(message)
		.setContentIntent(intent)
		.setSmallIcon(icon)
		.setLargeIcon(decodeImage(large_icon))
		.setWhen(when)
		.setStyle(new NotificationCompat.BigTextStyle()
				.bigText(message))
	.build();

In generateImageNotification() it will look like this:

Notification notification = new NotificationCompat.Builder(context)
		.setContentTitle(title)
		.setContentText(message)
		.setContentIntent(intent)
		.setSmallIcon(icon)
		.setLargeIcon(decodeImage(large_icon))
		.setWhen(when)
		.setStyle(new NotificationCompat.BigPictureStyle()
			.bigPicture(message_bitmap))
	.build();

In generateImageNotification() we also need to pass the large_icon parameter to generateNotification() in our failure case:

// If we didn't get the image, we're out of here
if (message_bitmap == null) {
	generateNotification(context
			, title
			, message
			, url
			, large_icon);
	return;
}

The two functions, in their entirety, are as follows:

private static void generateNotification(Context context
		, String title
		, String message
		, String url
		, String large_icon) {
	
	int icon = R.drawable.ic_stat_gcm;
	long when = System.currentTimeMillis();
	NotificationManager notificationManager = (NotificationManager)
			context.getSystemService(Context.NOTIFICATION_SERVICE);
			
	if ((title == null) || (title.equals(""))) {
		title = context.getString(R.string.app_name);
	}
	
	Intent notificationIntent = null;
	if ((url == null) || (url.equals(""))) {
		//just bring up the app
		notificationIntent = new Intent(context, DemoActivity.class);
	} else {
		//Launch the URL
		notificationIntent = new Intent(Intent.ACTION_VIEW);
		notificationIntent.setData(Uri.parse(url));
		notificationIntent.addCategory(Intent.CATEGORY_BROWSABLE);
	}
	
	// set intent so it does not start a new activity
	notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
			Intent.FLAG_ACTIVITY_SINGLE_TOP);
	PendingIntent intent =
			PendingIntent.getActivity(context, 0, notificationIntent, 0);
	
	Notification notification = new NotificationCompat.Builder(context)
			.setContentTitle(title)
			.setContentText(message)
			.setContentIntent(intent)
			.setSmallIcon(icon)
			.setLargeIcon(decodeImage(large_icon))
			.setWhen(when)
			.setStyle(new NotificationCompat.BigTextStyle()
					.bigText(message))
		.build();
	
	notification.flags |= Notification.FLAG_AUTO_CANCEL;
	notificationManager.notify(0, notification);
}

private static void generateImageNotification(Context context
		, String title
		, String message
		, String url
		, String image_url
		, String large_icon) {
	
	// The bitmap to download
	Bitmap message_bitmap = null; 
	// Should we download the image?
	if ((image_url != null) && (!image_url.equals(""))) {
		message_bitmap = AirBopImageDownloader.downloadBitmap(image_url);
	}
	// If we didn't get the image, we're out of here
	if (message_bitmap == null) {
		generateNotification(context
				, title
				, message
				, url
				, large_icon);
		return;
	}
			
	int icon = R.drawable.ic_stat_gcm;
	long when = System.currentTimeMillis();
	NotificationManager notificationManager = (NotificationManager)
			context.getSystemService(Context.NOTIFICATION_SERVICE);
	
	if ((title == null) || (title.equals(""))) {
		title = context.getString(R.string.app_name);
	}
	
	Intent notificationIntent = null;
	if ((url == null) || (url.equals(""))) {
		//just bring up the app
		notificationIntent = new Intent(context, DemoActivity.class);
	} else {
		//Launch the URL
		notificationIntent = new Intent(Intent.ACTION_VIEW);
		notificationIntent.setData(Uri.parse(url));
		notificationIntent.addCategory(Intent.CATEGORY_BROWSABLE);
	}
	
	// set intent so it does not start a new activity
	notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
			Intent.FLAG_ACTIVITY_SINGLE_TOP);
	PendingIntent intent =
			PendingIntent.getActivity(context, 0, notificationIntent, 0);
			
	Notification notification = new NotificationCompat.Builder(context)
			.setContentTitle(title)
			.setContentText(message)
			.setContentIntent(intent)
			.setSmallIcon(icon)
			.setLargeIcon(decodeImage(large_icon))
			.setWhen(when)
			.setStyle(new NotificationCompat.BigPictureStyle()
				.bigPicture(message_bitmap))
		.build();
	
	notification.flags |= Notification.FLAG_AUTO_CANCEL;
	notificationManager.notify(0, notification);
}   Intent notificationIntent = null;

Step 4 – Base64 Encoding the Image

The image that we will be sending down to our apps as part of our payload is this fantastic photo of me:

It's me at 49 x 49

There are two sites (I’m sure that there are others as well) that you can use to encode the data: WUtils.com or webcodertools.com. Both produce the same results, but I preferred webcodertools.com because the other added in newlines that I had manually to remove. The only problem with webcodertools.com is that you need to copy the correct text from the output.


We need to copy the text in between

" />

So we end up with only the image data as follows:

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

Step 5 - Sending the Message

Now we have all of the pieces that we need (the working app and the encoded image) in order to send our message via AirBop. To do this we need to go to our app and send the following custom JSON.

Note: If you are unfamiliar with how to send a Custom JSON message using AirBop please see Step 4 in the previous tutorial.

{
    "title": "AirBop",
    "message": "Mark has sent you a message from AirBop",
    "url": "http://www.airbop.com",
    "large_icon" : "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"
}

When the message gets to your client app it will look like the following:

Conclusion

That's it for this tutorial. You should now be able to push encoded images down to your app as part of the message payload. The one drawback to this method is the 4K message limit imposed by Google Cloud Messaging. So the images you want to use can only be so large before you run into that limit.

Here are a few other examples I worked on for this post.

Medieval RPG, where your attackers avatar shows up in the notification:

Sports Score app:

With the big picture style as well:

Some sort of strange grind-based RPG:

And we're done. Now you know another method for adding pictures to your AirBop push notifications. Good luck! Feel free to leave a question or a comment.