Android Cloud-to-Device Messaging: A Story

2011-04-14

A little background: at an exciting startup project, we're developing two main components -- a mobile client (a smartphone app) and a server. Currently, we've got an iOS (iPhone / iPad) client out, in a very early preview stage (more of an alpha than a beta) and we're building the Android side to match.

One of the big things we use -- and mention lots when talking to people we want to impress -- is push notifications: that is, our server doesn't wait for the user to check for updates, but rather proactively lets them know when there's something for their attention.

That sounds cool. So how does it work?

At a high level, your system has to talk to Apple's (for iPhones / iPads) or Google's (for Android devices) system, and Apple/Google then forwards a message to the specific device you had in mind. So you need to:

Thanks to a very experienced iPhone developer doing our iOS work, and an excellent post from a(nother) startup, it wasn't too much of a trial to set things up with Apple. The application on the device, when it starts up, asks the device for its address, and then sends that over to the server. The server can then send Apple a message using a slightly esoteric raw socket binary protocol, using that address, and Apple will forward it on. Finally, the application can tell the device it wants to receive those types of messages, and the device will wake it up and give it the message when one comes in.

The way Android does things is a bit more involved. The application, when it starts up, has to ask the device to ask Google for an address, and be willing to receive a message back from Google with that address. Once it's received the address, it can send that over to the server. The server, meanwhile, has to ask Google to validate its account details, and get a token that allows it to talk to Google. Once it's done that and has a device's address, the server can use the token and the address to send a message to Google. Finally, the application can tell the device it wants to receive those types of messages, and specifically how it wants to receive them, and the device will wake it up and give it the message.

OK, that seems logical enough. What went wrong?

To be fair to Google, it's only a few more steps, there's no esoteric raw socket binary protocol you have to speak, and the messages they allow are more flexible and powerful than Apple's. But combine a slightly inexperienced Android developer with sparse documentation and misleading examples and you have a bit of a problem. In my case, you have me diving into the operating system code to figure out why nothing works.

Ooh, interesting! Give me all the geeky details!

With an Android application, you have to tell the device about who you are and what you want to do in the application manifest, AndroidManifest.xml . Typically this is stuff like informing the device you need Internet access, or you want to be able to keep the phone from going to sleep (e.g. if you're a game). But if you want to receive push messages (or cloud-to-device messages, as Google calls them) you also have to declare that in your manifest.

First of all, you have to tell the device that you have created a component that will service your messages, using a <service> element, and inform the device of that component's name. Then you have to tell it how you expect it to service all received messages, and how to route yours correctly.

What's confusing is that, in order to make it all work, Google suggest using their prefabricated example code -- so you have to copy-and-paste their code into your project (which, as a developer, left a bad taste in my mouth from the start) and then intermingle references to their sample code and your own code. So your manifest should end up containing a section that looks like (of course, replace all references to the org.sample namespace with your own app's):

<service android:name="org.sample.C2DMReceiver" /> <!-- <<- THIS IS *YOUR* CLASS -->
<!-- Your class above should extend the provided 'com.google.android.c2dm.C2DMBaseReceiver'. 
Note that the name here is fully qualified, but if the class is in your package namespace, 
just a preceding dot will suffice (e.g. '.C2DMReceiver') -->

<receiver android:name="com.google.android.c2dm.C2DMBroadcastReceiver"
      android:permission="com.google.android.c2dm.permission.SEND"> <!-- <<- THIS IS
 *GOOGLE* CODE, COPIED IN FROM THE CHROME-TO-PHONE EXAMPLE -->
  <!-- This is for receiving messages -->
  <intent-filter>
      <action android:name="com.google.android.c2dm.intent.RECEIVE" />
      <category android:name="org.sample" /> <!-- <<- YOUR APP'S NAMESPACE HERE -->
  </intent-filter>
  <!-- This is for receiving your address (registration ID) from Google -->
  <intent-filter>
      <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
      <category android:name="org.sample" /> <!-- <<- YOUR APP'S NAMESPACE HERE -->
  </intent-filter>
</receiver>

Get all of that right, and implement the right methods on your registration and receiver classes (again, following closely from the examples, and using the right senderId as I discuss below) and it should all work, and you can avoid the hours of ClassCastExceptions and silently failing registration Intents that I waded through.

On the server side of things, while the documentation is sparse, at least it's not misleading. Essentially, your server needs to have a Google account. This Google account needs to be signed up to the service, and it also needs to be used as the senderId in your app code above.

What your server does in the meantime is request a ClientLogin authentication token from Google. This has to be saved somewhere, and while an FAQ says ClientLogins are valid for up to two weeks, it also says that validity varies by service. So, in short, your server has to be prepared for Google to arbitrarily expire your token, and has to be able to fetch a new one.

Finally, to send messages to your newly enabled Android application, your server sends HTTPS POST requests to Google using the registration ID your application got back from Google. (How to get the registration ID from the application to the server is left as an exercise for the reader ;-)). And that process, happily, is quite well documented.

All right then. What's next?

If anyone's read this far, thank you -- I needed to get that rant off my chest. Since Google do a huge amount to promote their developer-friendliness as a company, and their products do typically have very good documentation (the Android dev site is full of reference material), I was expecting more from them.

But I (and the startup project) will be pushing ahead! Funding is on the horizon, and we'd like to ramp up development. We are looking for smartphone and server developers -- so if you or anyone you know is interested in fast-moving, exciting, and possibly even paid work building iOS / Android apps and / or Python servers, please let me know!