John Hawthorn

Android Cloud to Device Messaging

Motivation

A project I’ve always had working in some form is a method of notifications from IRC on my phone. This, through bitlbee, also includes my instant messaging and twitter notifications. All of these solutions have been quick hacks, and I felt it wa time to write a speedy My previous solution was to forward any hilights over XMPP to receive notifications in google talk on my android phone. However this soon proved too much of an annoyance and notifications weren’t delivered as fast as I wanted. I’ve called my new solution generic_notify which I hope to be able to use like notify_send to send any sort of notification on the command line and have it appear on my phone.

C2DM basics

Android 2.2 “Froyo” introduced Cloud to Device Messaging (c2dm), a service to send small amounts of data to an android phone. It works by having the application register with the service and then having a remote server send a message to the c2dm google API, which is then forwarded to the device. A good reference and example application is chrometophone.

Currently, signing up for c2dm is required with the account used to send the messages.

Manifest

I used the com.google.android.c2dm library which can be found in the chrometophone source code. AndroidManifest.xml will need to be modified slightly to use it.

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.johnhawthorn.generic_notify" android:versionCode="11" android:versionName="2.2.0">

versionCode and versionName are set to these values since c2dm is for android 2.2 and up.

A couple of permissions are also needed

<!-- Only this application can receive the messages and registration result --> <permission android:name="com.johnhawthorn.generic_notify.permission.C2D_MESSAGE" android:protectionLevel="signature" /> <uses-permission android:name="com.johnhawthorn.generic_notify.permission.C2D_MESSAGE" /> <!-- This app has permission to register and receive message --> <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> <!-- App must have this permission to use the library --> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.USE_CREDENTIALS" />

And the following inside the application declaration

<application android:icon="@drawable/icon" android:label="@string/app_name"> <!-- Your application setup here --> <!-- In order to use the c2dm library, an application must declare a class with the name C2DMReceiver, in its own package, extending com.google.android.c2dm.C2DMBaseReceiver --> <service android:name=".C2DMReceiver" /> <!-- Only google service can send data messages for the app. If permission is not set - any other app can generate it --> <receiver android:name="com.google.android.c2dm.C2DMBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND"> <!-- Receive the actual message --> <intent-filter> <action android:name="com.google.android.c2dm.intent.RECEIVE" /> <category android:name="com.johnhawthorn.generic_notify" /> </intent-filter> <!-- Receive the registration id --> <intent-filter> <action android:name="com.google.android.c2dm.intent.REGISTRATION" /> <category android:name="com.johnhawthorn.generic_notify" /> </intent-filter> </receiver> </application>

Declaring the receiver

A class named C2DMReceiver needs to be declared which the com.google.android.c2dm library will call.

public class C2DMReceiver extends C2DMBaseReceiver{ public C2DMReceiver() { super("senderemail@gmail.com"); }

Next a couple methods are needed

@Override public void onRegistrered(Context context, String registration) { Log.i("GenericNotify", "registered and got key: " + registration); }

onRegistered is called registration with the c2dm service is successful. The registration key is received here and is usually sent to a server but for now it’s enough to log the key.

onMessage should also be declared which is called whenever a message is received.

@Override protected void onMessage(Context context, Intent intent) { Log.i("GenericNotifier", "onMessage called"); Bundle extras = intent.getExtras(); String message = (String)extras.get("message"); if(message != null){ Log.i("GenericNotifier", message); } }

Finally onError should deal with any errors.

Registering

Registering with the c2dm service is done by calling C2DMessaging.register. Registration needs only needs to be done the first time an application is run. Messages are received even when the application is not running and registration will persist across reboot

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String registrationId = C2DMessaging.getRegistrationId(this); if(registrationId != null && !"".equals(registrationId)){ Log.i("GenericNotifier", "Already registered. registrationId is " + registrationId); }else{ Log.i("GenericNotifier", "No existing registrationId. Registering.."); C2DMessaging.register(this, "senderemail@gmail.com"); } ... }

Testing

I tested by running the application and grabbing the registration id out of the logs. You can then grab a google ClientLogin Auth Token for the ac2dm service and then send messages using curl.

curl -d 'collapse_key=testing&registration_id=REGISTRATION_ID&data.message=testing' \ --header 'Authorization: GoogleLogin auth=[AUTH_TOKEN]' \ https://android.apis.google.com/c2dm/send