Wednesday, April 10, 2013

Android Push Notification with GCM



Push notification is a mechanism to solve to common issues found in mobile application development. A common use is to inform the user that an event has occurred on the server which may require the user to interact with the application. Another use is to update local state or inform the application to request updated state from the server.


The Android emulator is capable of receiving push notifications through GCM, however, it does require an additional step of actually logging a valid Google account into the emulator when it is running. This will need to be done for each emulator that you wish to use with push notifications through GCM.



Google Cloud Messaging

Google provides a service called Google Cloud Messaging (GCM) to provide push notifications for the Android platform. The developer console provided by google is accessed with a google account. Project management can be delegated to additional users, so it is good form to create the project under the account which will also be used in conjunction with releasing the application and delegate functionality to developers.



1. Create Project

Log into the Google APIs console (https://code.google.com/apis/console) to create a project. The “Overview” will display the project number. This is how client applications will identify themselves to the GCM service, in this case the Android application that will be using the GCM library.



2. Add GCM Service

Select the “Services” menu item. A list of Google provided services is presented. Enable the “Google Cloud Messaging for Android” by toggling the switch to on. This enables the service for the project.



3. Grand API Access

Select the “API Access” menu item. There will already be an existing “Key for browser apps (with referers)” which can be ignored. Click the “Create new Server key...” button. Enter the IP or subnet which will be allowed to send push notifications to clients. The “API Key” will be used by the server to authenticate it is allowed to send push notifications to the mobile devices.



Android Application

Adding push notification to an existing application is a straight forward task of adding the GCM library and updating the manifest. Once push notification is added to the application the developer is free to implement management of the registration and registration ids and handling the actual push notifications.



1. Download GCM Package

Using the Android SDK Manager, select the “Google Cloud Messaging for Android Library”, and install. This will download the library into $ANDROID_SDK/extras/google/gcm. Copy $ANDROID_SDK/extras/google/gcm/gcm-client/dist/gcm.jar to your project's $PROJECT/libs directory. Refresh the libs directory within Eclipse and add the gcm.jar to the build path.



2. Update Android Manifest

Several additions need to be added to the AndroidManifest.xml file to enable push notifications through GCM.



A new permission PACKAGE.permission.C2D_MESSAGE, where PACKAGE is the project's package needs to be defined and added. Additionally the RECEIVE and WAKE_LOCK are dependencies from the GCM library.


<!-- gcm permissions -->
<permission
    android:name="PACKAGE.permission.C2D_MESSAGE"
    android:protectionLevel="signature" />
<uses-permission android:name="PACKAGE.permission.C2D_MESSAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

Under the Application element add a service, which is created further along in the process. Replace PACKAGE with the project's package. Note, there appears to be a limitation that this service must reside in the root java package of the project. Attempts to change this caused GCM to stop functioning, probably based on some reflection aspects being used within the GCMBaseIntentService class.


<application...>
    <service android:name="PACKAGE.GCMIntentService"/>

Also under the Application element add the boilerplate for the a broadcast receiver which is used by the GCMIntentService which was defined above. Replace PACKAGE with the project's package.


<application...>
    <receiver
        android:name="com.google.android.gcm.GCMBroadcastReceiver"
        android:permission="com.google.android.c2dm.permission.SEND">
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
            <category android:name="PACKAGE" />
        </intent-filter>
    </receiver>

3. Hook GCM Registration

At some point the application will need to register itself with the server which is generating push notifications. In the case of applications which require login this is usually after the user has been authenticated. The logout process should always include mechanism to handle clearing the registration id from the server. Otherwise, applications which do not require the user to log in can register when convenient.



The GCM library provides a couple functions to verify the environment for the application is correctly setup. These methods will cause the application to log the error and exit. They can be left in for production, but are not necessary.


GCMRegistrar.checkDevice(context);
GCMRegistrar.checkManifest(context);

A registration id uniquely identifies the phone to the GCM servers for a project. Before requesting registration the application can check if there is a current registration id available. If the registration id exists the application can skip requesting a registration id. Otherwise a registration request is sent with the application's project id which will set into the motion a request to the GCM service for a registration id and completing with a call to the onRegistered method on the GCMIntentService.


String registrationId = GCMRegistrar.getRegistrationId(context);
if (registrationId.equals("")) {
    Log.d(getClass().getSimpleName(), "registering");
    GCMRegistrar.register(context, context.getString(R.string.project_id));
} else {
    Log.d(getClass().getSimpleName(), "registrationId: " + registrationId);
}

Unregistering an application from the GCM service will set into motion the handling of clearing the registration id from the GCM server and ending in the call to the onUnregister method on the GCMIntentService.

GCMRegistrar.unregister(context);



4. Extend GCMBaseIntentService

The interface to the GCM service is handled through implementing the abstract methods of the GCMBaseIntentService. There are registration methods, error handling methods, and receive message methods.



The registration id should be sent to the server which will be sending push notifications. That value will be used by the server to direct push notifications to the particular device. The only authoritative source for the registration id should are calls to GCMRegistrar.getRegistrationId and this value should not be stored locally to determine if the registration id is set. During the unregistration process the registration id should be cleared on the server.


@Override
protected void onRegistered(Context context, String content)
{
    Log.d(getClass().getSimpleName(), "onRegistered: " + content);
}

@Override
protected void onUnregistered(Context context, String content)
{
    Log.d(getClass().getSimpleName(), "onRegistered: " + content);
}

5. Push Notifications

The GCMIntentService is also the entry point for the push notification. The data will arrive within an Intent as an extra as a JSON encoded String. This string can be parsed and the data extracted.



@Override
protected void onMessage(Context context, Intent intent)
{
    String content = intent.getStringExtra(CONTENT);
    Log.i(getClass().getSimpleName(), "content: " + content);

    JSONObject json = new JSONObject(content);
    // ...
}

Since push notification data arrives asynchronously to the application UI thread, it is necessary to implement a mechanism to handle the data without a direct reference to an Activity. The application itself may not have any active running activities.



Example Application

The example application (https://github.com/crazydays/scratchpad/tree/master/dealer) uses a queueing message service which is bound by the GCMIntentService for when receiving data. The lifecycle of the GCMIntentService requires that the message queue service store messages when it stops and load them again when started. The application's activity binds and registers for notifications for when data arrives. Then the data is published to the web view if it has been initialized.