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:
- How to use the Airbop-Client sample
- How to download an image
- How to use the BigPictureStyle Notification
- How to send a JSON message on AirBop and parse the data
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.
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.