AirBopAndroidTutorial

Tutorial: Using AirBop to Push Images for BigPictureStyle Notifications

By December 5, 2012November 15th, 2013No Comments

Edit: If anyone is interested in the full source code please let me know.

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

Introduction

This tutorial will show you how you can use AirBop to push images down to your Android apps and have the images displayed in the notification area. The main topics covered in this tutorial are:

This is an AirBop tutorial. This tutorial assumes that you are already a little familiar with Google Cloud Messaging, AirBiop, and the AirBop-Client sample. You don’t need to know everything about them, but understanding what they are and how they work will make this tutorial much easier to follow.

This tutorial also assumes that you already have an AirBop account (which is free to setup and free up to 1000 devices), and that you computer is properly setup for Android development using Eclipse.

Note: BigPictureStyle notifications are only supported on Android 4.1 and higher. On older versions of the operating system only the standard notification will be displayed.

Step 0 – Get the AirBop Client Sample Working

For this introductory step I’m just going to follow the AirBop-Client README documentation.

Specifically the Get the AirBop Sample Project, Opening the AirBop Sample Project in Eclipse, and Detailed Installation and Compilation Example sections. They show you exactly what you need to do in order to get the AirBop-Client setup.

Next we will need to create a new AirBop app. You can read more about this in the Adding a New App knowledge base article.

After following those two steps, you should have the AirBop-Client app in Eclipse, and an AirBop app that represents this tutorial. Now we need the to find the AirBop App Key, App Secret, and Google Project Number values and add them to the client. You can follow the Getting Started with AirBop tutorial to get these values. One you have them you will need to insert them in the the proper sections of the CommonUtilities.java file as detailed here.

Now you should have an AirBop-Client that will compile, and when you run on your Android device it will register with the AirBop servers and be ready to receive messages from the cloud.

App registered with AirBop

Step 1 – Adding the Image Download Code

Now we are ready to add the code that will download the image for us. For this we will use code posted to the Android Developers blog a few years ago under the title: Multithreading For Performance. We are going to use the first two code blocks posted: the downloadBitmap function and the FlushedInputStream class. For simplicity, and because of the way messages are received in the app, we will not be implemented the full solution detailed on the blog post.

We are going to put his code into a new class called: AirBopImageDownloader to do this select the package in the package explorer and then select File | New | Class in Eclipse’s main menu to bring up the new Java Class dialog. Fill it out as follows:

Copy and paste the two code sections from the Google blog into the new class. There are a few additional code changes that need to be made in order for the code to work.

First we need to add the required imports:

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.http.AndroidHttpClient;
import android.util.Log;

We also need to change the FlushedInputStream class so that the int bytes variable name is correct:

int byte = read();
if (byte < 0) {
  break;  // we reached EOF
} else {
  bytesSkipped = 1; // we read one byte
}

Becomes:

int num_byte = read();
if (num_byte < 0) {
  break;  // we reached EOF
} else {
  bytesSkipped = 1; // we read one byte
}

There is also an error with a Log call in the downloadBitmap function, so change:

Log.w("ImageDownloader", "Error while retrieving bitmap from " + url, e.toString());

To:

Log.w("ImageDownloader", "Error while retrieving bitmap from " + url + e.toString());

Finally we need to use the FlushedInputStream (as explained in the blog post) when we create the Bitmap by changing:

final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);

To:

final Bitmap bitmap = BitmapFactory.decodeStream(new FlushedInputStream(inputStream));

When all is said and done the class will look like the following:

package com.airbop.client;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.http.AndroidHttpClient;
import android.util.Log;

public class AirBopImageDownloader {
	
	static public Bitmap downloadBitmap(String url) {
	    final AndroidHttpClient client = AndroidHttpClient.newInstance("Android");
	    final HttpGet getRequest = new HttpGet(url);

	    try {
	        HttpResponse response = client.execute(getRequest);
	        final int statusCode = response.getStatusLine().getStatusCode();
	        if (statusCode != HttpStatus.SC_OK) { 
	            Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url); 
	            return null;
	        }
	        
	        final HttpEntity entity = response.getEntity();
	        if (entity != null) {
	            InputStream inputStream = null;
	            try {
	                inputStream = entity.getContent(); 
	                final Bitmap bitmap = BitmapFactory.decodeStream(new FlushedInputStream(inputStream));
	                return bitmap;
	            } finally {
	                if (inputStream != null) {
	                    inputStream.close();  
	                }
	                entity.consumeContent();
	            }
	        }
	    } catch (Exception e) {
	        // Could provide a more explicit error message for IOException or IllegalStateException
	        getRequest.abort();
	        Log.w("ImageDownloader", "Error while retrieving bitmap from " + url + e.toString());
	    } finally {
	        if (client != null) {
	            client.close();
	        }
	    }
	    return null;
	}
	
	static class FlushedInputStream extends FilterInputStream {
	    public FlushedInputStream(InputStream inputStream) {
	        super(inputStream);
	    }

	    @Override
	    public long skip(long n) throws IOException {
	        long totalBytesSkipped = 0L;
	        while (totalBytesSkipped < n) {
	            long bytesSkipped = in.skip(n - totalBytesSkipped);
	            if (bytesSkipped == 0L) {
	                  int num_byte = read();
	                  if (num_byte < 0) {
	                      break;  // we reached EOF
	                  } else {
	                      bytesSkipped = 1; // we read one byte
	                  }
	           }
	            totalBytesSkipped += bytesSkipped;
	        }
	        return totalBytesSkipped;
	    }
	}
}

Step 2 – Responding to the Message and Downloading the Image

When we send the messages from AirBop we are going to be using JSON to pass our data. To read that JSON (now in the form of an Intent Bundle) we are going to have to edit the onMessage function in the GCMIntentService class. There we are going to define a new String variable that will store the image URL and then read that data out of the bundle. Once we have the data we will then send it to a new function called generateImageNotification which will be a duplicate of the generateNotification function with some our additional image handing:

String image_url = null;
...
image_url = bundle.getString("image_url");
...
generateImageNotification(context, title, message, url, image_url); 

The onMessage function will now look 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;
	
	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");
		} 
	}
	// If there was no body just use a standard message
	if (message == null) {
		message = getString(R.string.airbop_message);
	}	   	   		
	generateImageNotification(context, title, message, url, image_url); 	
}

Next we need to actually duplicate the generateNotification function (the one with the URL parameter), change the name, and add the additional image_url parameter:

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

The first task for our new function is to attempt to download the image using our AirBopImageDownloader class. If we cannot download the image then we are going to simply call the default generateNotification function and return:

// 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);
	return;
}

Step 3 - Creating the BigPictureStyle Notification

This step is actually very easy since we have all the pieces that we need and the AirBop-Client sample already uses the new Jelly-Bean style notifications. All we have to do is adjust the notification creation code to use the BigPictureStyle instead of the BigTextStyle and to pass it our already downloaded bitmap:

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

That's it. Now the generateImageNotification function is complete. Here is it in its entirety:

private static void generateImageNotification(Context context
		, String title
		, String message
		, String url
		, String image_url) {
	// 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);
		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)
			.setWhen(when)
			.setStyle(new NotificationCompat.BigPictureStyle()
				.bigPicture(message_bitmap))
		.build();
	
	notification.flags |= Notification.FLAG_AUTO_CANCEL;
	notificationManager.notify(0, notification);
}

Step 4 - The Message

That's it for the code, the app now does everything that we want it to, so the next step is to compose a message and sent it to our app. To do this we need to send a new message on AirBop, the Sending a New Message knowledge base article explains the basics of how to do this.

Our message is going to be a JSON message so the title we use for our message will just be a reference. We'll set the title to "First Image Push", and then set the "Mode" to JSON. This example message is going to send the AirBop logo down to the app. The JSON to do this looks very similar to the JSON in the Sending a New Message knowledge base article, except that the JSON values are different and we added the image_url parameter:

{
    "title": "AirBop",
    "message": "Here is your BigPictureStyle notification",
    "url": "http://www.airbop.com",
    "image_url" : "https://www.andromo.com/blog/wp-content/uploads/2012/12/a.1003-small-size.jpg"
}

The JSON should be pretty self explanatory, it is sending a standard AirBop message (title, message, url) down to the app with the additional URL of the image that we will use in the BigPictureStyle notification. Notice that the image_url parameter corresponds with the string we used to load the image URL out of the bundle in the onMessage function earlier:

image_url = bundle.getString("image_url");

So let's send this message now, by setting the Send at value to Now and pressing that lovely blue Send Message Now button at the bottom of the Send Message page.

Step 5 - The Result

Now that we have sent the message we can relax and wait for it to arrive in the next few milli-seconds. Once it does we should see the following:

When the user clicks on the image they will be sent to the url parameter that we passed in the JSON.

If the user shrinks the notification then they will see our message text:

That's it! Now you know one method for adding pictures to your AirBop push notifications. Good luck! Feel free to leave a question or a comment.