Google Cloud Messaging GCM for Android and Push Notifications

Last modified on September 7th, 2014 by Joe.

Google cloud messaging (GCM) is an Android platform API provided by Google for sending and receiving push notifications to and from an Android application. This is one of the most important arsenal in an Android developer’s armory.

Let us consider an email android application, it is not efficient to have the application ping the server to check for updates regularly. Mobile devices’ resources should to be used wisely as they are scarce.

Push and pull are two types of notifications using which messages are transferred. In this android tutorial, I will walk you through setting up push notification setup between an application server and an Android mobile application using Google cloud messaging (GCM) API.

Google Cloud Messaging GCM API

We can use GCM as a notification engine. On an event it will send a notification to an android application and vice-versa.

A notification is a small piece of information. Using GCM, maximum 4kb of payload can be sent in a notification.

For now GCM is a free service – lets use responsibly :-)

Android App can receive the message from Google cloud messaging server (GCM) even if the app is not running via Intent broadcasting (our example app will demonstrate this).

Getting Started with GCM

This tutorial will have a complete working setup having,

Part A – A custom web application running in an application server. This will connect to Google GCM server and post notification. Part B – An application running in an Android virtual device (emulator) with an Intent capable of receiving the push notification from the GCM server.

Interacting with Google cloud messaging server (GCM) is not difficult, all we need to do is get couple of configurations perfect and code wise just few number of lines. Overall setup involves IP Address, network proxy/firewall, API key and json objects which sounds like it will be difficult. Get a cup of green tea, start this on a positive note and we will get this done in half an hour.

Part A – GCM Server Application

Custom web application will connect to Google cloud messaging API url and send notification message to GCM server to push it to the registered Android device.

Prerequisite for Custom Application

Google API Server Key GCM RegId of the Android Device to communicate via GCM

1. Google API Server Key

Create Google Project : Go to the URL https://cloud.google.com/console/project then create a project and navigate into it. Enable Google Cloud Messaging For Android : Go to menu “APIs & auth –> APIs” and switch on. Create Server Key : Go to Menu “Credentials” and under the section Public API access, click Create New Key. There are different types of keys and here we should use “Sever Key”. Edit Allowed IPs : This is to white-list a set of IP addresses as the server key is tied to IP addresses for security. To get your public IP address, in Google search type ‘my public ip address’. Click ‘Edit allowed IPs’ and update your IP.

2. GCM RegId of the Android Device to communicate

RegId uniquely identifies the Android device to communicate. From the Android app (client), we will make the registration and get the RegId from the Google GCM server. Then, we should communicate that RegId to the web application via some defined process.

In this tutorial, let us provide a callback url in the web application and the Android device should invoke that url to share GCM RegId. In the web application, get the RegId and persist it to a file.

GCM Server Application in Java & PHP

The example presented with this tutorial demonstrates minimal features and in future tutorials we will see about advanced capabilities of GCM.

I have written server application for Google cloud messaging in both Java and PHP. You can use either Java or PHP.

GCM Server Application in PHP

In PHP its just a single file web application, just put it in your Apache.

<?php //generic php function to send GCM push notification function sendPushNotificationToGCM($registatoin_ids, $message) { //Google cloud messaging GCM-API url $url = 'https://android.googleapis.com/gcm/send'; $fields = array( 'registration_ids' => $registatoin_ids, 'data' => $message, ); // Google Cloud Messaging GCM API Key define("GOOGLE_API_KEY", "AIzaSyDA5dlLInMWVsJEUTIHV0u7maB82MCsZbU"); $headers = array( 'Authorization: key=' . GOOGLE_API_KEY, 'Content-Type: application/json' ); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields)); $result = curl_exec($ch); if ($result === FALSE) { die('Curl failed: ' . curl_error($ch)); } curl_close($ch); return $result; } ?> <?php //this block is to post message to GCM on-click $pushStatus = ""; if(!empty($_GET["push"])) { $gcmRegID = file_get_contents("GCMRegId.txt"); $pushMessage = $_POST["message"]; if (isset($gcmRegID) && isset($pushMessage)) { $gcmRegIds = array($gcmRegID); $message = array("m" => $pushMessage); $pushStatus = sendPushNotificationToGCM($gcmRegIds, $message); } } //this block is to receive the GCM regId from external (mobile apps) if(!empty($_GET["shareRegId"])) { $gcmRegID = $_POST["regId"]; file_put_contents("GCMRegId.txt",$gcmRegID); echo "Ok!"; exit; } ?> <html> <head> <title>Google Cloud Messaging (GCM) Server in PHP</title> </head> <body> <h1>Google Cloud Messaging (GCM) Server in PHP</h1> <form method="post" action="gcm.php/?push=1"> <div> <textarea rows="2" name="message" cols="23" placeholder="Message to transmit via GCM"></textarea> </div> <div><input type="submit" value="Send Push Notification via GCM" /></div> </form> <p><h3><?php echo $pushStatus; ?></h3></p> </body> </html>

/gcm/gcm.php is the home page using which we can send a push notification to Google cloud messaging server. /gcm/gcm.php?shareRegId=1 is the callback url and can be invoked from an external application (like the client Android app), which wants to share the GCM Registration id (RegId). On invocation, RegId will be written to a file.

Note: PHP is optional. If you prefer Java, use the following as server.

GCM PHP Server Application Output

GCM Server Application in Java

For this tutorial, I have implemented a HTTP based App Server and have used gcm-server a Helper library for GCM HTTP server operations. It has only two files. Deploy and run using Tomcat. You can download this as a web application project bundle below.

index.jsp

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <% String pushStatus = ""; Object pushStatusObj = request.getAttribute("pushStatus"); if (pushStatusObj != null) { pushStatus = pushStatusObj.toString(); } %> <head> <title>Google Cloud Messaging (GCM) Server in PHP</title> </head> <body> <h1>Google Cloud Messaging (GCM) Server in Java</h1> <form action="GCMNotification" method="post"> <div> <textarea rows="2" name="message" cols="23" placeholder="Message to transmit via GCM"></textarea> </div> <div> <input type="submit" value="Send Push Notification via GCM" /> </div> </form> <p> <h3> <%=pushStatus%> </h3> </p> </body> </html>

GCMNotification.java

package com.javapapers.java.gcm; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.google.android.gcm.server.Message; import com.google.android.gcm.server.Result; import com.google.android.gcm.server.Sender; @WebServlet("/GCMNotification") public class GCMNotification extends HttpServlet { private static final long serialVersionUID = 1L; // Put your Google API Server Key here private static final String GOOGLE_SERVER_KEY = "AIzaSyDA5dlLInMWVsJEUTIHV0u7maB82MCsZbU"; static final String MESSAGE_KEY = "message"; public GCMNotification() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Result result = null; String share = request.getParameter("shareRegId"); // GCM RedgId of Android device to send push notification String regId = ""; if (share != null && !share.isEmpty()) { regId = request.getParameter("regId"); PrintWriter writer = new PrintWriter("GCMRegId.txt"); writer.println(regId); writer.close(); request.setAttribute("pushStatus", "GCM RegId Received."); request.getRequestDispatcher("index.jsp") .forward(request, response); } else { try { BufferedReader br = new BufferedReader(new FileReader( "GCMRegId.txt")); regId = br.readLine(); br.close(); String userMessage = request.getParameter("message"); Sender sender = new Sender(GOOGLE_SERVER_KEY); Message message = new Message.Builder().timeToLive(30) .delayWhileIdle(true).addData(MESSAGE_KEY, userMessage).build(); System.out.println("regId: " + regId); result = sender.send(message, regId, 1); request.setAttribute("pushStatus", result.toString()); } catch (IOException ioe) { ioe.printStackTrace(); request.setAttribute("pushStatus", "RegId required: " + ioe.toString()); } catch (Exception e) { e.printStackTrace(); request.setAttribute("pushStatus", e.toString()); } request.getRequestDispatcher("index.jsp") .forward(request, response); } } }

GCM Java Server Application Output

Download GCM Java Server Application

Following zip file contains the GCM Java server web application eclipse project.

Part B – GCM Android Client Application

Following Android application is a GCM client and it will,

Register with GCM server and receive GCM-RegId.

Share the GCM-RegId with the application server.

Receive push notification from the GCM server and alert user.

GCM Android App Prerequisite

Google Play Services in SDK Google APIs Google Play Services Lib project as Android dependency Google Project Id Register/login with a Google account in the AVD

1. Google Play Services in SDK: is required for Google cloud messaging. Check your Android SDK Manager if you have installed it. It is in the bottom below Extras in the SDK Manager as shown below.

2. Google APIs: we need “Google APIs” installed. I am using Android 4.3 with API 18. Check under your Android API version for Google API.

3. Google-Play-Services_Lib project as Android dependency: Import Google play services lib project into workspace (if you use Android Studio skip this step). In Eclipse, Click File > Import, select Android > Existing Android Code into Workspace. This project should be available as part of the android-sdk download. After importing we need to add this project as dependency to the Android application we are going to create.

4. Google Project Id: In the previous part during custom web application, we created a project in the Google console for creating the Google API server key. Google will assign a project Id when doing that. We need to use that Google project Id to register this Android application with the GCM server.

5. Register/login with a Google account in the AVD: We need to create an Android virtual device (emulator) with the Target as Google API. Then start the emulator, go to list of apps, click ‘Settings’, then ‘Add account’ and login with your Google/Gmail id.

GCM Android Client Application

The complete project with source is given for download below.

RegisterActivity.java

We have two buttons in this activity, first one is to register this Android application with the Google cloud messaging server and second one is to share the RegId with the web application.

On click of the Register button, we will use the Google Project ID to register with Google cloud messaging server and get the GCM RegId.

Second button is to share this RegId with our custom web application. On click the custom application’s callback url will be invoked. I am running the web application in same machine localhost. But we should not use localhost in the url, since we are connecting from the Android virtual device, as it will loopback to the AVD itself. We should use the our local-ip.

After sharing the Google cloud messaging RegId to web application, we will forward to the next activity.

package com.javapapers.android; import java.io.IOException; import android.app.Activity; import android.content.Context; import android.content.Intent; 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.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; import com.google.android.gms.gcm.GoogleCloudMessaging; public class RegisterActivity extends Activity { Button btnGCMRegister; Button btnAppShare; GoogleCloudMessaging gcm; Context context; String regId; public static final String REG_ID = "regId"; private static final String APP_VERSION = "appVersion"; static final String TAG = "Register Activity"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_register); context = getApplicationContext(); btnGCMRegister = (Button) findViewById(R.id.btnGCMRegister); btnGCMRegister.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { if (TextUtils.isEmpty(regId)) { regId = registerGCM(); Log.d("RegisterActivity", "GCM RegId: " + regId); } else { Toast.makeText(getApplicationContext(), "Already Registered with GCM Server!", Toast.LENGTH_LONG).show(); } } }); btnAppShare = (Button) findViewById(R.id.btnAppShare); btnAppShare.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { if (TextUtils.isEmpty(regId)) { Toast.makeText(getApplicationContext(), "RegId is empty!", Toast.LENGTH_LONG).show(); } else { Intent i = new Intent(getApplicationContext(), MainActivity.class); i.putExtra("regId", regId); Log.d("RegisterActivity", "onClick of Share: Before starting main activity."); startActivity(i); finish(); Log.d("RegisterActivity", "onClick of Share: After finish."); } } }); } public String registerGCM() { gcm = GoogleCloudMessaging.getInstance(this); regId = getRegistrationId(context); if (TextUtils.isEmpty(regId)) { registerInBackground(); Log.d("RegisterActivity", "registerGCM - successfully registered with GCM server - regId: " + regId); } else { Toast.makeText(getApplicationContext(), "RegId already available. RegId: " + regId, Toast.LENGTH_LONG).show(); } return regId; } private String getRegistrationId(Context context) { final SharedPreferences prefs = getSharedPreferences( MainActivity.class.getSimpleName(), Context.MODE_PRIVATE); String registrationId = prefs.getString(REG_ID, ""); if (registrationId.isEmpty()) { Log.i(TAG, "Registration not found."); return ""; } int registeredVersion = prefs.getInt(APP_VERSION, Integer.MIN_VALUE); int currentVersion = getAppVersion(context); if (registeredVersion != currentVersion) { Log.i(TAG, "App version changed."); return ""; } return registrationId; } private static int getAppVersion(Context context) { try { PackageInfo packageInfo = context.getPackageManager() .getPackageInfo(context.getPackageName(), 0); return packageInfo.versionCode; } catch (NameNotFoundException e) { Log.d("RegisterActivity", "I never expected this! Going down, going down!" + e); throw new RuntimeException(e); } } private void registerInBackground() { new AsyncTask () { @Override protected String doInBackground(Void... params) { String msg = ""; try { if (gcm == null) { gcm = GoogleCloudMessaging.getInstance(context); } regId = gcm.register(Config.GOOGLE_PROJECT_ID); Log.d("RegisterActivity", "registerInBackground - regId: " + regId); msg = "Device registered, registration ID=" + regId; storeRegistrationId(context, regId); } catch (IOException ex) { msg = "Error :" + ex.getMessage(); Log.d("RegisterActivity", "Error: " + msg); } Log.d("RegisterActivity", "AsyncTask completed: " + msg); return msg; } @Override protected void onPostExecute(String msg) { Toast.makeText(getApplicationContext(), "Registered with GCM Server." + msg, Toast.LENGTH_LONG) .show(); } }.execute(null, null, null); } private void storeRegistrationId(Context context, String regId) { final SharedPreferences prefs = getSharedPreferences( MainActivity.class.getSimpleName(), Context.MODE_PRIVATE); int appVersion = getAppVersion(context); Log.i(TAG, "Saving regId on app version " + appVersion); SharedPreferences.Editor editor = prefs.edit(); editor.putString(REG_ID, regId); editor.putInt(APP_VERSION, appVersion); editor.commit(); } }

MainActivity.java

package com.javapapers.android; import android.app.Activity; import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.widget.Toast; public class MainActivity extends Activity { ShareExternalServer appUtil; String regId; AsyncTask shareRegidTask; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); appUtil = new ShareExternalServer(); regId = getIntent().getStringExtra("regId"); Log.d("MainActivity", "regId: " + regId); final Context context = this; shareRegidTask = new AsyncTask () { @Override protected String doInBackground(Void... params) { String result = appUtil.shareRegIdWithAppServer(context, regId); return result; } @Override protected void onPostExecute(String result) { shareRegidTask = null; Toast.makeText(getApplicationContext(), result, Toast.LENGTH_LONG).show(); } }; shareRegidTask.execute(null, null, null); } }

GcmBroadcastReceiver.java

package com.javapapers.android; 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) { ComponentName comp = new ComponentName(context.getPackageName(), GCMNotificationIntentService.class.getName()); startWakefulService(context, (intent.setComponent(comp))); setResultCode(Activity.RESULT_OK); } }

GCMNotificationIntentService.java

This Android intent service extends the IntentService.

package com.javapapers.android; 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 GCMNotificationIntentService extends IntentService { public static final int NOTIFICATION_ID = 1; private NotificationManager mNotificationManager; NotificationCompat.Builder builder; public GCMNotificationIntentService() { super("GcmIntentService"); } public static final String TAG = "GCMNotificationIntentService"; @Override protected void onHandleIntent(Intent intent) { Bundle extras = intent.getExtras(); GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this); String messageType = gcm.getMessageType(intent); if (!extras.isEmpty()) { 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)) { for (int i = 0; i < 3; i++) { Log.i(TAG, "Working... " + (i + 1) + "/5 @ " + SystemClock.elapsedRealtime()); try { Thread.sleep(5000); } catch (InterruptedException e) { } } Log.i(TAG, "Completed work @ " + SystemClock.elapsedRealtime()); sendNotification("Message Received from Google GCM Server: " + extras.get(Config.MESSAGE_KEY)); Log.i(TAG, "Received: " + extras.toString()); } } GcmBroadcastReceiver.completeWakefulIntent(intent); } private void sendNotification(String msg) { Log.d(TAG, "Preparing to send notification...: " + 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.gcm_cloud) .setContentTitle("GCM Notification") .setStyle(new NotificationCompat.BigTextStyle().bigText(msg)) .setContentText(msg); mBuilder.setContentIntent(contentIntent); mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build()); Log.d(TAG, "Notification sent successfully."); } }

Config.java

package com.javapapers.android; public interface Config { // used to share GCM regId with application server - using php app server static final String APP_SERVER_URL = "http://192.168.1.17/gcm/gcm.php?shareRegId=1"; // GCM server using java // static final String APP_SERVER_URL = // "http://192.168.1.17:8080/GCM-App-Server/GCMNotification?shareRegId=1"; // Google Project Number static final String GOOGLE_PROJECT_ID = "512218038480"; static final String MESSAGE_KEY = "message"; }

ShareExternalServer.java

package com.javapapers.android; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import android.content.Context; import android.util.Log; public class ShareExternalServer { public String shareRegIdWithAppServer(final Context context, final String regId) { String result = ""; Map paramsMap = new HashMap (); paramsMap.put("regId", regId); try { URL serverUrl = null; try { serverUrl = new URL(Config.APP_SERVER_URL); } catch (MalformedURLException e) { Log.e("AppUtil", "URL Connection Error: " + Config.APP_SERVER_URL, e); result = "Invalid URL: " + Config.APP_SERVER_URL; } StringBuilder postBody = new StringBuilder(); Iterator > iterator = paramsMap.entrySet() .iterator(); while (iterator.hasNext()) { Entry param = iterator.next(); postBody.append(param.getKey()).append('=') .append(param.getValue()); if (iterator.hasNext()) { postBody.append('&'); } } String body = postBody.toString(); byte[] bytes = body.getBytes(); HttpURLConnection httpCon = null; try { httpCon = (HttpURLConnection) serverUrl.openConnection(); httpCon.setDoOutput(true); httpCon.setUseCaches(false); httpCon.setFixedLengthStreamingMode(bytes.length); httpCon.setRequestMethod("POST"); httpCon.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); OutputStream out = httpCon.getOutputStream(); out.write(bytes); out.close(); int status = httpCon.getResponseCode(); if (status == 200) { result = "RegId shared with Application Server. RegId: " + regId; } else { result = "Post Failure." + " Status: " + status; } } finally { if (httpCon != null) { httpCon.disconnect(); } } } catch (IOException e) { result = "Post Failure. Error in sharing with App Server."; Log.e("AppUtil", "Error in sharing with App Server: " + e); } return result; } }

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.javapapers.android" android:versionCode="1" android:versionName="1.0" > <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <permission android:name="com.javapapers.android.permission.C2D_MESSAGE" android:protectionLevel="signature" /> <uses-permission android:name="com.javapapers.android.permission.C2D_MESSAGE" /> <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.VIBRATE" /> <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="16" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".RegisterActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="com.javapapers.android.MainActivity" android:configChanges="orientation|keyboardHidden" android:label="@string/app_name" > </activity> <receiver android:name=".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="com.javapapers.android" /> </intent-filter> </receiver> <service android:name=".GCMNotificationIntentService" /> </application> </manifest>

Android GCM Client Dependencies

On sending a successful push notification to Google cloud messaging server:

On successful receive of notification from GCM:

Download GCM Android Client Application

Following zip file contains the Android application eclipse project.

Checklist – Things that may go wrong: