I was forced to use GCM, so here are my notes. GCM is a mechanism/service for sending push notifications from a server to Android apps. It was made public on June 26, 2012. Before that, it was implemented using C2DM. This is a simple sample, but I’ll keep it as a memo.

It’s available here.

Partially quoted from http://developer.android.com/google/gcm/index.html and http://www.kotemaru.org/2013/07/28/android-push-message.html.

Mechanism

  • Let’s call Google’s server the GCM server
  • Let’s call our own server the self-server
  1. First, obtain the device ID (registration ID) from the GCM server.
  2. Send the registration ID to the self-server.
  3. From the self-server, set the registration ID and send a message to the GCM server.
  4. The GCM server sends the message to each device, and a push notification is sent.

Preparation

First, obtain the Google API from https://code.google.com/apis/console/.

  1. From the Service list, turn the status of Google Cloud Messaging for Android to on.
  2. From API ACCESS, select Create new Server Key to obtain an API Key.

Here, check two pieces of information:

  • API Key: Used when sending a message from the self-server to the GCM server. It’s written in Key for server apps. Something like “Jfdsah82…”.
  • Sender id: Used when sending a message from the app to the self-server. It’s the number after project in the URL. Something like “4352…”.

1. Client Implementation

Although old, I implemented it with Google APIs 10. First, install GoogleCloudMessaging for Android Library from Android SDK Manager. Next, add {Android SDK directory}/extras/google/gcm/gcm-client/dist/gcm-src.jar to your project. (You can add it by right-clicking in the src directory => Archive File)

Manifest File

Add permissions to the manifest file as follows. Please add the parts surrounded by BEGIN and END. Change com.example.com to your project’s package name.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.gcm_reciever"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="10" />
    
    <!-- BEGIN: for GCM_reciever -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    
    <permission android:name="com.example.gcm_reciever.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />
    <uses-permission android:name="com.example.gcm_reciever.permission.C2D_MESSAGE" />
    <!-- END: for GCM_reciever -->

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.gcm_reciever.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <!-- BEGIN: for GCM_reciever -->
        <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" />
                <category android:name="com.example.gcm_reciever" />
				<action android:name="com.google.android.c2dm.intent.REGISTRATION"/>
			<category android:name="com.example.gcm_reciever"/>
			</intent-filter>
        </receiver>
        <service android:name=".GCMIntentService" />
        <!-- END: for GCM_reciever -->
    </application>

</manifest>

MainActivity.java

package com.example.gcm_reciever;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

import com.google.android.gcm.GCMRegistrar;

import android.net.Uri;
import android.net.http.AndroidHttpClient;
import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;

public class MainActivity extends Activity {

	/* Self-server URL */
	public static final String url = "http:/hogehoge.net/closet/gcm.php";
	
	/* sender id */
	public static final String senderIds = "xxxxxxxxxx";
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		GCMRegistrar.checkDevice(this);
		GCMRegistrar.checkManifest(this);
		
		/* Obtain the registration ID from the GCM server.
		 * If already registered, send it to the self-server.
		 * If not registered, register with the GCM server using GCMResistrar.register().
		 * (It will be sent to the self-server at the Registered timing)
		 */
		String regId = GCMRegistrar.getRegistrationId(getApplicationContext());
		if(regId.equals("")){
			Log.i("GCM", "onCreate call register");
			GCMRegistrar.register(getApplicationContext(), senderIds);
		}else{
			Log.i("GCM", "onCreate call sendMyserver");
			sendMyServer(regId);
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

    @Override
    protected void onDestroy() {
        GCMRegistrar.onDestroy(this);
        super.onDestroy();
    }
	
	/**
	 * Send the registration ID to the self-server.
	 * @param regId Registration ID
	 */
	static public void sendMyServer(String regId){
		
		HttpClient client = new DefaultHttpClient();
		List parameters = new ArrayList();
		parameters.add(new BasicNameValuePair("action", "reg"));
		parameters.add(new BasicNameValuePair("regId", regId));

		String query = URLEncodedUtils.format(parameters, "UTF-8");
		HttpGet get = new HttpGet(url + "?" + query);
		
		Log.i("GCM", "sendMyServer: regId="+regId);

		try {
			HttpResponse response = client.execute(get);
			Log.i("GCM", "sending my server success.");
		} catch (ClientProtocolException e) {
			e.printStackTrace();
			Log.i("GCM", "sending my server failed.");
		} catch (IOException e) {
			e.printStackTrace();
			Log.i("GCM", "sending my server failed.");
		}
	}

}

GCMIntentService.java

package com.example.gcm_reciever;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;

import com.google.android.gcm.GCMBaseIntentService;
import com.example.gcm_reciever.*;

public class GCMIntentService extends GCMBaseIntentService {

   private String tag = "GCM";

    public GCMIntentService() {
        super(MainActivity.senderIds);
    }

    /**
     * Called when the registration ID is registered with the GCM server.
     * Here, we also send it to the self-server.
     */
    @Override
    protected void onRegistered(Context context, String regId) {
        Log.i(tag, "onRegistered: regId = " + regId);
        MainActivity.sendMyServer(regId);
    }

    /**
     * Called when a message is received from the GCM server.
     */
    @Override
    protected void onMessage(Context context, Intent intent) {
    	Bundle extras = intent.getExtras();
    	String message = extras.getString("message");
        Log.i(tag, "onMessage: msg = " + message);
    }

    @Override
    public void onError(Context context, String errorId) {
        Log.i(tag, "onError: " + errorId);
    }

    @Override
    protected boolean onRecoverableError(Context context, String errorId) {
        Log.i(tag, "onRecoverableError: " + errorId);
        return super.onRecoverableError(context, errorId);
    }

	@Override
	protected void onUnregistered(Context context, String registrationId) {
		// TODO Auto-generated method stub
		
	}
}

2. Server Implementation

<?php
require_once "HTTP/Request.php";

if(!ISSET($_GET['action']))
        exit(0);

if($_GET['action'] == "reg"){
        $fp = fopen("id.txt", "w");
        fwrite($fp, $_GET['regId']);
        fclose($fp);
        print('saved');

}else if(($_GET['action']) == "send"){

        $fp = fopen("id.txt", "r");
        $regid = fgets($fp);
        fclose($fp);

        $apikey = "AIza....";
        $msg = 'posted from gcm';

        $rq = new HTTP_Request("https://android.googleapis.com/gcm/send");
        $rq->setMethod(HTTP_REQUEST_METHOD_POST);
        $rq->addHeader("Authorization", "key=".$apikey);
        $rq->addPostData("registration_id", $regid);
        $rq->addPostData("collapse_key", "1");
        $rq->addPostData("data.message", $msg);

        if (!PEAR::isError($rq->sendRequest())) {
                print "\n" . $rq->getResponseBody();
        } else {
                print "\nError has occurred";
        }
}

3. Error Codes

Here are some errors I experienced and how to deal with them.

java.lang.IllegalArgumentException: Receiver not registered: com.google.android.gcm.GCMBroadcastReceiver@40673a10

=> When registering with GCM, modify the context like GCMRegistrar.onDestroy(getApplicationContext());.

ACCOUNT_MISSING

=> This occurs if the device is not logged in with a Google account. Particular attention is needed for emulators.