C2DM – A simple introduction (Using a Java server)

26-01-2012 14:55

C2DM stands for Cloud to Device Messaging, and is an easy way for developers to use Google’s framework to push messages to Android devices – no persistent data connection required! “But that’s not easy at all” I hear you say. “Or possible!” you add. And of course, you are half right: There is a persistent connection, but it’s nothing you need to care about. Google maintains a data connection to Android devices for all those nice to have features like push email and Google Talk notifications. All you are doing is piggybacking on this connection to send your own messages to the device. Because this feature wasn’t added until Android SDK 8 you will not be able to use this feature before Android version 2.2.
 
So what can we send? Very simple messages only, and not too many of them at that. Google has a hard upper limit of 1024 bytes – roughly 1024 ASCII characters – for each message. This is just enough for a small message or an ID that the device use to make a direct request to your 3rd party server. Google is very vague about how much traffic is allowed through C2DM so your only guideline is that you should not send too many messages. If you do, you are instructed to back off for a bit. So no large essays, no images, and if you try to send your private mp3 collection through C2DM Google will most likely send a very menacing individual to your door who will shout very colorful words at you for a while.
 
Before you can do anything you need to go sign up. This is a very painless process and all you need is a valid Gmail account. As your server will be logging in with this account you should probably create a dedicated account for this specific purpose.
 
Now for a quick introduction to the process. Everything from initialization to the first message only takes five steps. Ok, five and a half if you need to be prickly about it, but let’s say five. First of all your Android device needs to register itself with the C2DM server. The bounty? The server will contact the device back with a registration ID! The Android device sends this ID to the 3rd party server along with whatever identifier the server needs to know whose ID it is receiving. And that is step three of the process dealt with!
 
tl_files/download/blog/c2dm/c2dm.pngNow for that pesky half step: The 3rd party server logs in to the C2DM server for a ClientLogin auth token that is used to verify that the push messages come from the right server. When that is out of the way, the 3rd party server can send its message to the C2DM server using the registration ID from step 1 to identify the device. The C2DM server subsequently relays the message to the Android device, and the circle is complete! For further messages only steps 3 and 4 are necessary unless the Android device or the 3rd party server is signed out. More on that later.
 
To reiterate:

  1. Android device requests registration ID from C2DM server.   
  2. C2DM server sends registration ID to Android device.
  3. Android device sends registration ID to 3rd party server.
  4. 3rd party server sends message to C2DM server with registration ID as target device.
  5. C2DM relays message to Android device

 
Step 1 tl_files/download/blog/c2dm/screenshot.png

Now that we know what we need to do, let’s start by getting the registration ID. To keep this simple we’ll make just a single Activity with two buttons: Register and Unregister.
 
When pressing the Register button we start an Intent that will communicate with the C2DM server:
 
Intent registrationIntent = new Intent("com.google.android.c2dm.intent.REGISTER");
registrationIntent.putExtra("app", PendingIntent.getBroadcast(this, 0, new Intent(), 0));
registrationIntent.putExtra("sender", C2DM_EMAIL);
startService(registrationIntent);
 
Now the C2DM_EMAIL is the email of the Gmail account that you registered earlier. Nevermind the “app” line, it’s simply a way to tell the C2DM intent which application is registering. It needs to be there and it should not be changed.

 
Step 2

In order to receive the registration ID we need to set up an intent receiver in the AndroidManifest.xml file. In step 3 we will also need to send messages to our server, so let’s just set up the manifest file. As this was introduced in SDK 8, our C2DM test requires this:
 
<uses-sdk android:minSdkVersion="8" />
 
Also, we need access to the internet:
 
<uses-permission android:name="android.permission.INTERNET" />
 
We then need to make sure that only our application is able to receive messages intended for our application. To do this we first define a permission that is unique to our application, and then use it. You need change the permission name to point to your own package here.
 
<permission android:name="com.zylinc.test.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="com.zylinc.test.permission.C2D_MESSAGE" />
 
Our application should also have permission to receive C2DM messages all together:
 
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
 
In our application xml we need to define the BroadcastReceiver that receives both the messages and the registration ID from C2DM. Let’s call It C2DMReceiver:
 
<application>
    <receiver android:name=".C2DMReceiver" android:permission="com.google.android.c2dm.permission.SEND">
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            <category android:name="com.zylinc.test" />
        </intent-filter>
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
            <category android:name="com.zylinc.test" />
        </intent-filter>
    </receiver>
    ...
</application>
 
That’s it for the xml. Time to implement the BroadcastReceiver. Create a class called C2DMReceiver, let it extend BroadcastReceiver and extend onReceive. Here you can get one of the two actions that you just added to your receiver’s intent filter: Receive or Registration:
 
@Override
public void onReceive(Context context, Intent intent) {
    this.mContext = context;
    String action = intent.getAction();
    if(action.equals("com.google.android.c2dm.intent.REGISTRATION")){
        handleRegistration(intent);
    } else if (action.equals("com.google.android.c2dm.intent.RECEIVE")){
        handleMessage(intent);
    }
}
 
If your registration failed for some reason you should get the “error” stringExtra from the Intent and react appropriately. Errors are defined on the Google C2DM page.
 
If there is no error you may have unregistered. In this case there is an “unregistered” stringExtra in the Intent.
 
If you have not been unregistered, look for the registration_id stringExtra in the Intent. If you have this, you have completed step two, and can move on to step three: Sending the ID to your server! I will not cover this, as I do not know how to communicate with your server. I will get back to handling the message in step 5.

 
Step 4, part one

Unless you for some reason are signed out, you only need to get a registration ID once for your server. As we have made our server in Java, this tutorial will cover that language. Implementations in other languages are available elsewhere online. What we need to send to the C2DM server in order to log in is the email and password in order to authenticate, an ID identifying your server as well as some extra data specifying what we are trying to do:
 
String getClientLogin(){
    HttpResponse res = null;
    DefaultHttpClient client = new DefaultHttpClient();
    try {
        java.net.URI uri = new java.net.URI("https://www.google.com/accounts/ClientLogin");
        List params = new ArrayList();
        params.add(new BasicNameValuePair("accountType", "HOSTED_OR_GOOGLE"));
        params.add(new BasicNameValuePair("Email", APPLICATION_EMAIL));
        params.add(new BasicNameValuePair("Passwd", APPLICATION_PASSWORD));
        params.add(new BasicNameValuePair("service", "ac2dm"));
        params.add(new BasicNameValuePair("source", SERVER_NAME));
        HttpPost post = new HttpPost(uri);
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, "UTF-8");
        post.setEntity(entity);
        res = client.execute(post);

        if(res != null){
            if(res.getEntity() != null){
                String response = EntityUtils.toString(res.getEntity());
                String[] respArr = response.split("\n");
                for(String s : respArr){
                    if(s.startsWith("Auth=")){
                        return s.split("=")[1];
                    }else if(s.startsWith("Error=")){

                    }
                }
            }
        }
        return null;
    } catch (URISyntaxException e) {
    } catch (UnsupportedEncodingException e) {
    } catch (ClientProtocolException e) {
    } catch (IOException e) {
    }
    return null;
}
 
If everything goes smoothly this will give you a brand new clientLogin for your server to use.

 
Step 4, part two

Now that the server has a ClientLogin it can go ahead and send a message to the C2DM server. If you want your Android device to receive a message you would put it in the attribute data.message. This will show up on your device as the message extra in your BroadcastReceiver. That is, send data.[something] and you can get it by the [something] stringExtra in the intent you receive. But let’s just send a message. Aside from that the server needs to identify the target device with its registration id. Finally a collapse key is needed. This is an ID that says that two messages basically are the same, but only need to be delivered once. For instance if you tell the device that it has new mail waiting for it, there is no need to send that same message several times, if the device hasn’t gotten the first message yet. So if the collapse key is the same, several undelivered messages will be collapsed, and just one will be delivered. With these three variables the server can send the message. All that is missing is authorizing the servers identity with the clientLogin from part one. The final code looks like this:
 
boolean c2dm(String id, String clientLogin){
    String url = "https://android.apis.google.com/c2dm/send";
    DefaultHttpClient client = new DefaultHttpClient();
    List params = new ArrayList();
    params.add(new BasicNameValuePair("data.message", "Hello, C2DM!"));
    params.add(new BasicNameValuePair("registration_id", id));
    params.add(new BasicNameValuePair("collapse_key", type));
    UrlEncodedFormEntity entity;
    try {
        entity = new UrlEncodedFormEntity(params, "UTF-8");
        HttpPost post = new HttpPost(url);
        post.setEntity(entity);
        post.addHeader("Authorization", "GoogleLogin auth=" + clientLogin);
        HttpResponse res = client.execute(post);
        if(res != null){
            if(res.getEntity() != null){
                String response = EntityUtils.toString(res.getEntity());
                String[] respArr = response.split("\n");
                for(String s : respArr){
                    if(s.startsWith("Error=")){
                        if(s.contains("NotRegistered")){
                            // User is not registered. Delete from map
                        }else{
                            // Handle other error
                        }
                    }
                }
                return true;
            }
        }
    } catch (UnsupportedEncodingException e) {
    } catch (ClientProtocolException e) {
    } catch (IOException e) {
    }
    return false;
}
 
If this goes well, the C2DM server now has the message and will send it to the device as soon as possible. Note that there are also other options that might prove useful. For more information on them, go to the C2DM page.

 
Step 5

Finally we need to receive the message on the Android device. We actually set this up in step 2, so there is little to add. We can finally make that handleMessage() method:
 
void handleMessage(Intent intent){
    String message = intent.getStringExtra("message");
    Log.d("Zylinc", "Message recieved: " + message);
    Toast.makeText(mContext, "C2DM says: " + message, Toast.LENGTH_LONG).show();
}
 
And that’s it. You can now expand on this example and send custom messages that will launch your (or other’s) activities, instruct the device to fetch updates or… Whatever you want, really.

 
Worth noting

There are a few things that should be noted at this point:
 
  • At any point the C2DM server can unregister your phone. The silver lining is that you get a notification and are free to register the device again.
  • If your server sends too many requests it will be throttled and asked to back off exponentially.
  • For this to work your phone needs to be connected to the Google services. So obviously this won’t work, if your device has turned off data, or is otherwise unable to connect to Google. Also, when getting data connectivity it might take a while to establish the connection to Google.
  • Under the best circumstances the message will be delivered within a couple of seconds, but this is by no means guaranteed. It all depends on the connection of the device.
  • It is not possible at this time to send bulk messages to a large amount of phones.

 
That being said, this opens up new opportunities without exposing the devices to more battery drain. Now go be creative!

 

Mark Gjøl is an M.Sc from The Technical University of Denmark, the creator of the Android gallery Floating Image, and works with Android, Windows and Tomcat applications.

Go back

Add a comment