Wednesday, August 27, 2014

Google Cloud Messaging for Android (GCM) with example

Table of Content


             Creating a Google API project
                          Enabling the GCM Service
                          Obtaining an API Key
             Implementing GCM Client
                          Step 1: Setup Google Play Services
                          Step 2 : Application’s manifest configuration
                          Step 3: Write Your Application
                                       MainActivity layout xml
                                       Receive a message
             Implementing a GCM Server
                          Project Setup
                          Writing code to send message to android device
             Sample App Downloads:
                          Android sample app
                          Java server application for sending GCM messages

GCM Concept

Google Cloud Messaging for Android (GCM) is a free service that helps developers send data from servers to their Android applications on Android devices, and upstream messages from the user's device back to the cloud (upstream messages are only supported in XMPP).

The message could be a ligh message telling the Android application that there is new data to be fetched from the server (for instance, a "new email" notification informing the application that it is out of sync with the back end), or it could be a message containing up to 4kb of payload data (so apps like instant messaging can consume the message directly).

Entities involved in GCM

Components : The entities that play a primary role in GCM.
Credentials : The IDs and tokens that are used in different stages of GCM to ensure that all parties have been authenticated, and that the message is going to the correct place.

Components

Client App
The GCM-enabled Android application that is running on a device. This must be a 2.2 Android device that has Google Play Store installed, and it must have at least one logged in Google account if the device is running a version lower than Android 4.0.4. Alternatively, for testing you can use an emulator running Android 2.2 with Google APIs.
3rd-party Application Server
An application server that you write as part of implementing GCM. The 3rd-party application server sends data to an Android application on the device via the GCM connection server.
GCM Connection Servers
The Google-provided servers involved in taking messages from the 3rd-party application server and sending them to the device.
Credentials

Sender ID
A project number you acquire from the API console. The sender ID is used in the registering android device to connection server.
Application ID
The Android application that is registering to receive messages. The Android application is identified by the package name from the manifest. This ensures that the messages are targeted to the correct Android application.
Registration ID
An ID issued by the GCM servers to the Android application that allows it to receive messages. Once the Android application has the registration ID, it sends it to the 3rd-party application server, which uses it to identify each device that has registered to receive messages for a given Android application. In other words, a registration ID is tied to a particular Android application running on a particular device. Note that registration IDs must be kept secret.


Note: If you use backup and restore, you should explicitly avoid backing up registration IDs. When you backup a device, apps backup shared prefs indiscriminately. If you don't explicitly exclude the GCM registration ID, it could get reused on a new device, which would cause delivery errors.
Google User Account
For GCM to work, the mobile device must include at least one Google account if the device is running a version lower than Android 4.0.4.
Sender Auth Token
An API key that is saved on the 3rd-party application server that gives the application server authorized access to Google services. The API key is included in the header of POST requests that send messages.

Implementation:

Before you begin, make sure to set up the Google Play Services SDK. You need this SDK to use the GoogleCloudMessaging methods.

Creating a Google API project

The first thing is to get the sender ID described above. Its the project number of the project created in Google Developer Console.

You have to create a new project there and have to get the project number. This would be your GCM Sender ID to be used in registering android device to GCM.

In the sidebar on the left, select APIs & auth.
In the displayed list of APIs, turn the Google Cloud Messaging for Android toggle to ON.


1) In the sidebar on the left, select APIs & auth > Credentials.
2) Under Public API access, click Create new key.
3) In the Create a new key dialog, click Server key.
4) In the resulting configuration dialog, supply your server's IP address. For testing purposes, you can use 0.0.0.0/0
5) Click Create.
6) In the refreshed page, copy the API key. You will need the API key later on to perform authentication in your application server.

Note: If you need to rotate the key, click Regenerate key. A new key will be created. If you think the key has been compromised and you want to delete it immediately, click Delete.


Setup is done. Now you have to implement below components.

1) GCM Server : Java application which will be hosted on application server. This will send messages to GCM connection servers, which in turn deliver messages to android application running on android devices.
2) GCM Client : Android application
3) Decide which GCM connection server you want to use (HTTP or XMPP)


Implementing GCM Client

A GCM client is a GCM-enabled app that runs on an Android device.

We will implement a below simple android application which will register with gcm connection server and generate registration id to be used by 3rd party application server for sending the message to the same android device.


gcm sample android application

This android app must have below 2 functionalities.

1) It must include a code to register android device to GCM connection server and thereby get a registration ID.
2) It must include a broadcast receiver to receive messages sent by GCM server.

Step 1: Setup Google Play Services

To write your client application, we will use GoogleCloudMessaging API. 
To use this API, you must set up your project to use the Google Play services SDK, as described in Setup Google Play Services SDK setup.

Also if you are using android emulator to test the gcm application, then you have to use emulator for android version >= 4.1.2 . 
and while creating AVD you have to select Google APIs as a target. 
If you don’t have Google APIs not available as a target, then open SDK manager and download the same. Once you make any change in SDK manager (install/remove packages), then you have to restart your eclipse to make those changes available in AVD manager.

1) Following meta tag must be present under application tag. This is required if you are using google play services SDK.

<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />

2) Add permission : com.google.android.c2dm.permission.RECEIVE


<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />


This is required so the application can register to get registration id and to receive messages sent by GCM server.

3) Add permission : android.permission.INTERNET

<uses-permission android:name="android.permission.INTERNET" />

4) Add permission : android.permission.GET_ACCOUNTS

<uses-permission android:name="android.permission.GET_ACCOUNTS" />

This is to get user account information as GCM requires a Google account (necessary only if if the device is running a version lower than Android 4.0.4).

5) An applicationPackage + ".permission.C2D_MESSAGE" permission to prevent other Android applications from registering and receiving the Android application's messages. The permission name must exactly match this pattern—otherwise the Android application will not receive the messages.

lets consider applicatonPackage as "com.example.gcm" , then the entry would be like : 

<permission android:name="com.example.gcm.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="com.example.gcm.permission.C2D_MESSAGE" />

6) A broadcast receiver for getting messages sent by 3rd party application server. This should be an instance of WakefulBroadcastReceiver.

The receiver should require the com.google.android.c2dm.SEND permission, so that only the GCM Framework can send a message to it.

<receiver android:name=".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" />  <!-- application package name -->
        </intent-filter>
</receiver>

7) A Service (typically an IntentService) to which the WakefulBroadcastReceiver passes off the work of handling the GCM message, while ensuring that the device does not go back to sleep in the process.

You could choose to process your messages in a regular BroadcastReceiver instead, but realistically, most apps will use a IntentService.

<service android:name=".GcmIntentService" />

8) If the GCM feature is critical to the Android application's function, be sure to set android:minSdkVersion="8" or higher in the manifest. This ensures that the Android application cannot be installed in an environment in which it could not run properly.

Step 3: Write Your Application

Finally write your application.
In this section we will only see how to register to GCM connection server and get registration Id using GoogleCloudMessaging APIs. 

This contains one edittext which will hold registration id. and one button onclick of which we will the register application to GCM.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="${relativePackage}.${activityClass}" >

    <TextView
        android:id="@+id/lbl_reg_id"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="GCM Registration ID: " />

    <EditText
        android:id="@+id/txt_reg_id"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/lbl_reg_id"/>

    <Button
        android:id="@+id/btn_reg_device"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/txt_reg_id"
        android:text="Get Registration ID"
        android:onClick="onClickRegDevice" />

</RelativeLayout>

MainActivity.java (Google Play Services APK compatibility check on android device and GCM registration)

Following functionalities have been implemented in this.
1) Checking whether android device has google play service APK. If not then displaying appropriate dialog box to download and install it.
2) Registering with GCM to get registration ID to be used by 3rd party application.

package com.example.gcm;

import java.io.IOException;

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.gcm.GoogleCloudMessaging;

public class MainActivity extends Activity
{
 private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
 public static final String PROPERTY_REG_ID = "registration_id";
 private static final String PROPERTY_APP_VERSION = "appVersion";

 private static String SENDER_ID = "601094561";
 private GoogleCloudMessaging gcm = null;

 private EditText txt_reg_id = null;

 @Override
 protected void onCreate(Bundle savedInstanceState)
 {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  txt_reg_id = (EditText) this.findViewById(R.id.txt_reg_id);

  if (checkPlayServices())
  {
  }
  else
  {
   Toast.makeText(this, "This device is not supported.", Toast.LENGTH_LONG).show();
  }
 }

 public void onClickRegDevice(View view)
 {
  System.out.println("in onClickRegDevice.. view = " + view);

  // get registration id from shared preferences and thus check whether app is already registered or not.
  String regid = getRegistrationId(this);
  Log.i(this.toString(), "registration id from shared pref : " + regid);

  if (regid.isEmpty()) // if blank, then app is not yet registered
  {
   registerInBackground();
  }
  else
  {
   txt_reg_id.setText(regid);
  }
   
 }

 /**
  * this method will check whether device has google play services apk installed or not. if not then it will display appropriate dialog.
  */
 private boolean checkPlayServices()
 {
  int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
  if (resultCode != ConnectionResult.SUCCESS)
  {
   if (GooglePlayServicesUtil.isUserRecoverableError(resultCode))
   {
    GooglePlayServicesUtil.getErrorDialog(resultCode, this, PLAY_SERVICES_RESOLUTION_REQUEST).show();
   }
   else
   {
    Log.i(this.toString(), "This device is not supported.");
    finish();
   }
   return false;
  }
  return true;
 }

 /**
  * get gcm registration id from shared preferences. once app is registered to gcm and got the registration id, it will store that in shared
  * preferences.
  * 
  * returns blank when no registration id found in shared pref. if app was updated, then also it will return blank so that app can be registered
  * again.
  */
 private String getRegistrationId(Context context)
 {
  final SharedPreferences prefs = getGCMPreferences(context);
  String registrationId = prefs.getString(PROPERTY_REG_ID, "");
  if (registrationId.isEmpty())
  {
   Log.i(this.toString(), "Registration not found.");
   return "";
  }
  // Check if app was updated; if so, it must clear the registration ID
  // since the existing regID is not guaranteed to work with the new app version.
  int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
  int currentVersion = getAppVersion(context);
  if (registeredVersion != currentVersion)
  {
   Log.i(this.toString(), "App version changed.");
   return "";
  }
  return registrationId;
 }

 /**
  * store registration id and app version in shared preferences.
  */
 private void storeRegistrationId(Context context, String regId)
 {
  final SharedPreferences prefs = getGCMPreferences(context);

  int appVersion = getAppVersion(context);
  Log.i(this.toString(), "Saving regId on app version " + appVersion);

  SharedPreferences.Editor editor = prefs.edit();
  editor.putString(PROPERTY_REG_ID, regId);
  editor.putInt(PROPERTY_APP_VERSION, appVersion);
  editor.commit();
 }

 private SharedPreferences getGCMPreferences(Context context)
 {
  // This sample app persists the registration ID in shared preferences, but
  // how you store the regID in your app is up to you.
  return getSharedPreferences(MainActivity.class.getSimpleName(), Context.MODE_PRIVATE);
 }

 private static int getAppVersion(Context context)
 {
  try
  {
   PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
   return packageInfo.versionCode;
  }
  catch (NameNotFoundException e)
  {
   throw new RuntimeException("Could not get package name: " + e);
  }
 }

 private void registerInBackground()
 {
  new AsyncTask<String, Void, String>()
  {
   private String regId = "";

   @Override
   protected String doInBackground(String... params)
   {
    String msg = "";
    try
    {
     if (gcm == null)
     {
      gcm = GoogleCloudMessaging.getInstance(MainActivity.this);
     }
     regId = gcm.register(SENDER_ID);
     Log.i(this.toString(), "regId = "+regId);

     // You should send the registration ID to your server over HTTP,
     // so it can use GCM/HTTP or CCS to send messages to your app.
     // The request to your server should be authenticated if your app
     // is using accounts.
     // sendRegistrationIdToBackend();

     // For this demo: we don't need to send it because the device
     // will send upstream messages to a server that echo back the
     // message using the 'from' address in the message.

     // Persist the regID - no need to register again.
     storeRegistrationId(MainActivity.this, regId);
     
     msg = regId;
    }
    catch (IOException ex)
    {
     msg = "Error :" + ex.getMessage();
     // If there is an error, don't just keep trying to register.
     // Require the user to click a button again, or perform
     // exponential back-off.
    }
    return msg;
   }

   @Override
   protected void onPostExecute(String msg)
   {
    // setting registration id in edit text.
    txt_reg_id.setText(msg);
   }

  }.execute(null, null, null);
 }
}

Receive a message

Whenever your 3rd party application sends a message to GCM enabled android app, GCM framework will invoke WakefulBroadcastReceiver. so its WakefulBroadcastReceiver's responsibility to handle the message.
A WakefulBroadcastReceiver is a special type of broadcast receiver that takes care of creating and managing a partial wake lock for your app, so that your application go to sleep while processing the message.

WakefulBroadcastReceiver will invoke IntentService, which will be actually processing the message.

Following is the code for WakefulBroadcastReceiver. it will start the GcmIntentService when it will receive the message.

package com.example.gcm;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.support.v4.content.WakefulBroadcastReceiver;

public class GcmBroadcastReceiver extends WakefulBroadcastReceiver
{
 @Override
 public void onReceive(Context context, Intent intent)
 {
  // Explicitly specify that GcmIntentService will handle the intent.
  ComponentName comp = new ComponentName(context.getPackageName(), GcmIntentService.class.getName());
  
  // Start the service, keeping the device awake while it is launching.
  startWakefulService(context, (intent.setComponent(comp)));
  
  setResultCode(Activity.RESULT_OK);
 }
}

GcmIntentService will process the message according to the application requirement.
Here it will only send a notifications.

package com.example.gcm;

import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import com.google.android.gms.gcm.GoogleCloudMessaging;

public class GcmIntentService extends IntentService
{
 public static final int NOTIFICATION_ID = 1;
 private NotificationManager mNotificationManager;
 NotificationCompat.Builder builder;

 public GcmIntentService()
 {
  super("GcmIntentService");
 }

 @Override
 protected void onHandleIntent(Intent intent)
 {
  Bundle extras = intent.getExtras();
  GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);

  // The getMessageType() intent parameter must be the intent you received
  // in your BroadcastReceiver.
  String messageType = gcm.getMessageType(intent);

  if (!extras.isEmpty())
  { // has effect of unparcelling Bundle
   /*
    * Filter messages based on message type. Since it is likely that GCM will be extended in the future with new message types, just ignore
    * any message types you're not interested in, or that you don't recognize.
    */

   if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType))
   {
    sendNotification("Send error: " + extras.toString());
   }
   else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType))
   {
    sendNotification("Deleted messages on server: " + extras.toString());    
   }
   else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) // If it's a regular GCM message, do some work.
   {
    // This loop represents the service doing some work.
    for (int i = 0; i < 5; i++)
    {
     Log.i(this.toString(), "Working... " + (i + 1) + "/5 @ " + SystemClock.elapsedRealtime());
     try
     {
      Thread.sleep(5000);
     }
     catch (InterruptedException e)
     {
     }
    }
    Log.i(this.toString(), "Completed work @ " + SystemClock.elapsedRealtime());
    
    // Post notification of received message.
    sendNotification("Received: " + extras.toString());
    
    Log.i(this.toString(), "Received: " + extras.toString());
   }
  }
  // Release the wake lock provided by the WakefulBroadcastReceiver.
  GcmBroadcastReceiver.completeWakefulIntent(intent);
 }
 
 // Put the message into a notification and post it.
 // This is just one simple example of what you might choose to do with
 // a GCM message.
 private void sendNotification(String msg)
 {
  mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);

  PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);

  NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this).setSmallIcon(R.drawable.ic_launcher)
    .setContentTitle("GCM Notification").setStyle(new NotificationCompat.BigTextStyle().bigText(msg)).setContentText(msg);

  mBuilder.setContentIntent(contentIntent);
  mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
 }
}

Implementing a GCM Server


Project Setup

Download helper libraries from : http://code.google.com/p/gcm/source/browse/#git%2Fgcm-server

Create a new java project in eclipse (gcm-server-sample)

Copy gcm-server/src/com folder to gcm-server-sample/src directory.

Create a new folder lib into gcm-server-sample project.

Copy gcm-server/lib/json_simple-1.1.jar file to gcm-server-sample/lib directory.

Open gcm-server-sample project properties. goto Java Build Path --> Libraries tab and add json_simple-1.1.jar file to build path.

Writing code to send message to android device

Create a new class /gcm-server-Sample/src/test/GCMServerTest.java . in this class we will write a code to send a message to android device.

package test;

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

import com.google.android.gcm.server.Message;
import com.google.android.gcm.server.MulticastResult;
import com.google.android.gcm.server.Sender;

public class GCMServerTest
{
 public static void main(String[] args)
 {
  try
  {
   sendMessage();
   System.out.println("message sent");
  }
  catch (Exception e)
  {
   e.printStackTrace();
  }
 }
 
 private static void sendMessage() throws IOException
 {
  String API_KEY = ""; //sender id got from google api console project
  String collpaseKey = "gcm_message"; //if messages are sent and not delivered yet to android device (as device might not be online), then only deliver latest message when device is online
  String messageStr = "message content here"; //actual message content
  String messageId = "APA91bGgGzVQWb88wkRkACGmHJROeJSyQbzLvh3GgP2CASK_NBsuIXH15HcnMta3e9ZXMhdPN6Z3FSD2Pezf6bhgUuM2CF74SgZbG4Zr57LA76VVaNvSi7XM7QEuAVLIiTsXnVq3QAUFDo-ynD316bF10JGT3ZOaSQ"; //gcm registration id of android device
  
  Sender sender = new Sender(API_KEY);
  Message.Builder builder = new Message.Builder();
  
  builder.collapseKey(collpaseKey);
  builder.timeToLive(30);
  builder.delayWhileIdle(true);
  builder.addData("message", messageStr);
  
  Message message = builder.build();
  
  List<String> androidTargets = new ArrayList<String>();
  //if multiple messages needs to be deliver then add more message ids to this list
  androidTargets.add(messageId);
  
  MulticastResult result = sender.send(message, androidTargets, 1);
  System.out.println("result = "+result);
  
  if (result.getResults() != null) 
  {
   int canonicalRegId = result.getCanonicalIds();
   System.out.println("canonicalRegId = "+canonicalRegId);
   
   if (canonicalRegId != 0) 
   {
            }
  }
  else 
  {
   int error = result.getFailure();
   System.out.println("Broadcast failure: " + error);
  }
 }
}

Sample App Downloads


Android sample app

You have to update your sender id (Google API project number)

Java server application for sending GCM messages

You have to update API key and registration id of android device to send message in source code.



1 comment:

Creating and Deploying Java Web Application on AWS using Elastic Beanstalk

This tutorial is for creating simple java web application using eclipse and then deploying it on AWS cloud. Video tutorial for creating/de...