Saturday, November 6, 2010

C2DM: On The Receiving End

My last couple of posts regarding Android's Cloud to Device Messaging framework were purely from the perspective of the server, in that we first had to obtain an authentication token using ClientLogin, and then we actually used the C2DM service to send a message.  But what about the client side?  What's needed from our Android app in order to receive these near-realtime notifications?  Well as it turns out, we simply need to do a couple of things:
  • Register with the C2DM framework to receive messages
  • Pass the registration ID that we get to the server
All in all, that's a pretty small to-do list, don't ya think? :)

Warming Up
Of course, before we can even start to think about using the C2DM framework, we have to do a few things first to make sure our app is fully prepared.  As the ability to use the C2DM framework arrived in the Froyo release, we have to make sure that our application requires version 2.2 or later by specifying it in the manifest:


    
    

    ...

Next, we need to make sure that we have our permissions set up properly. In order to receive notifications, the app must have the following permissions:
  • com.google.android.c2dm.permission.RECEIVE - allows us to register and receive messages.
  • android.permission.INTERNET - we need this so that we can pass the registration key to the third-party server that will be sending us notifications (it needs to know who to send the messages to, right?).
  • <applicationPackage>.permission.C2D_MESSAGE
  • - stops other applications from trying to register as our app in order to steal our messages.
This should be straightforward enough, so let's have a go:


    
    
    

    
    

    
    

    ...

So far, so simple, right?  Good. :)  But, we're not quite out of the woods as far as the permissions go just yet; we still need to make sure that we only receive messages from the C2DM framework, otherwise any other app could send us notifications (which in all likelihood we probably don't want to have happen).  To achieve this, we'll specify a receiver that has the following permissions (each with a category that matches our applicationPackage):
  • com.google.android.c2dm.intent.REGISTRATION
  • com.google.android.c2dm.intent.RECEIVE
So what does this look like?  Well, something like this:


    
    
        
        
            
            
        
        
        
            
            
        
    

    ...

Here, we're specifying a receiver (my.example.app.C2DMReceiver) that will perform the handling of the registration ID and any messages that are sent our way.  Again, all in all relatively straightforward...don't ya just love Android? :)

Registration
Now that we have the permissions sorted, it's time to register our app so that it can receive messages...and here's where it gets super-silly-easy.  All we need to do is create an Intent (com.google.android.c2dm.intent.REGISTER) and pass the email address of the sender (which will be the email address that we set up for the server to use) and our application ID:
public static void register(Context context, String senderEmail) {
    Intent registrationIntent = new Intent("com.google.android.c2dm.intent.REGISTER");
    registrationIntent.putExtra("app",
            PendingIntent.getBroadcast(context, 0, new Intent(), 0));
    registrationIntent.putExtra("sender", senderEmail);
    context.startService(registrationIntent);
}
Wow, pretty easy huh?  (Don't worry, I can sense the nodding) :)  So what happens after we've fired off that Intent?  Well this is where the receiver that we declared in the manifest comes in to play:
public class C2DMReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION")) {
            String registrationId = intent.getStringExtra("registration_id");
            String error = intent.getStringExtra("error");
            String removed = intent.getStringExtra("unregistered");
            
            if (error != null) {
                // Perform error processing
                ...
            } else if (removed != null) {
                // Perform any post-unregistration cleanup
                ...
            } else if (registrationId != null) {
                // Send registrationId to third-party server
                ...
            }
        }
    }

}
Our receiver simply checks the Intent to see if it's a com.google.android.c2dm.intent.REGISTRATION Intent, and if it is then it grabs the registration_id and, barring any errors (or if this is a response to our application unregistering itself), it goes ahead and passes the received registration ID to our third-party server.  This now ensures that our app is primed and ready to start receiving messages.

Receiving Messages
Handling any messages that come in is done using the same means that we used for receiving the registration ID.  This means we're going to use C2DMReceiver again...specifically, we're going to be using exactly the same method:
public class C2DMReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        ...
        if (intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")) {
            // Yay, we've received a message...grab the
            // parameters and do awesome stuff!
            String myParam1 = intent.getStringExtra(SOME_PARAM_1);
            String myParam2 = intent.getStringExtra(SOME_OTHER_PARAM);
            // Perform any processing
            if ("some_condition".equalsIgnoreCase(myParam1)) {"
                doSomethingAwesome(myParam2);
            }
            ...
        }
    }

}
And in a nutshell, that's it...it just about couldn't get any simpler.  Really.  But I'm not quite done with C2DM yet; there are some things that are handy to know when it comes to integration testing...

Testing, Testing, 1-2-3
It's relatively simple to test things out just by using the emulator alone.  As always though, there are some things that we need to make sure of first, and these are:
  • The AVD (Android Virtual Device) must use the package "Google APIs by Google Inc., Android API 8", so make sure you have it installed.  It has to be "Google APIs" and not simply "Android API 8" or it won't work.
  • C2DM requires a valid signed-in Google account in order to establish a connection with the device, which means that the emulator also needs to have a valid Google account that is logged in.  Simply go to Settings > Accounts & Sync > Add Account and provide the account details.
  • If the application server that you will be sending the messages from is running locally, make sure you're not trying to use localhost when sending the registration ID in your app.  Instead, use address 10.0.2.2 to hit the local server instance and save yourself some noodle-scratching. :)
Happy messaging!

No comments:

Post a Comment