Google Cloud Messaging GCM CCS with XMPP

As part of Google Cloud Messaging (GCM), Google provides an XMPP endpoint and that is called the Google Cloud Connection Server (CCS). It provides support for persistent asynchronous and most importantly bidirectional connection. When we use HTTP server, we can send only downstream messages to Android devices using Google GCM server. With Google CCS, we can send upstream message from an Android device to server and subsequently to another Android device.

In the Android device, the same connection that is used to receive the message is reused while sending an upstream message and this is an advantage in terms of resource usage. This is the best choice for implementing an Android chat application. I will develop a sample Android chat application and post it in the next tutorial. In this tutorial, we will see about how a device can send message to a XMMP server application via the GCM CCS and vice versa.

If you are new to Google Cloud Messaging, I recommend you to go through the introductory tutorial which discusses about notifications in Android using Google cloud messaging. This linked introductory tutorial explains the Google GCM concept with an example and it covers the prerequisite for using the Google Cloud infrastructure. You will need the same prerequisite for running the example given in this tutorial.

Google-GCM-CCS-XMPP-Messaging

  1. PART A (Registration): Android device sends request to the Google CCS to register and obtain a RegId.
  2. PART A: Google CCS responds back with the RegId.
  3. PART B (Upstream): Device sends a message to CCS server.
  4. PART B: The message sent in the previous step is relayed by the Google CCS server to the XMMP server application.
  5. PART C (Downstream): XMPP sends a downstream message to the CCS.
  6. PART C: Google CCS relays the downstream message to the registered Android device.

Google CCS XMMP Upstream Messaging Prerequisites

  1. Project in Google Developers Console
  2. Server API Key
  3. Google Approval for API access

For 1 and 2, please refer the notifications in Android using Google cloud messaging tutorial.

For Google Approval you need to sign up using this form: https://services.google.com/fb/forms/gcm/

It took three weeks for me to get approval and it may vary depending on the request queue.

Google CCS XMPP Upstream Messaging Server

Server part is completely different than in the previous examples. We need to provide and implementation for XMPP server. Doing that from ground up is difficult and so let us use the smack library. Google also uses the same library in its examples.

For this implementation I have borrowed the Google sample code and made some customizations to it.

This example handles three cases.

  1. If the there is a message pair with key as ‘action’ and value as ‘register’ in payload, then grabs the RegId from the message payload and persists in a file for future use.
  2. If the there is a message pair with key as ‘action’ and value as ‘echo’ in payload, then sends the same message back to the sender device.
  3. Send a downstream message to a registered Android device.

SmackCcsClient.java

/*
 * Most part of this class is copyright Google.
 * It is from https://developer.android.com/google/gcm/ccs.html
 */
package com.javapapers.java.gcm;

import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketInterceptor;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.DefaultPacketExtension;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.util.StringUtils;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;
import org.xmlpull.v1.XmlPullParser;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ssl.SSLSocketFactory;

/**
 * Sample Smack implementation of a client for GCM Cloud Connection Server.
 *
 * For illustration purposes only.
 */
public class SmackCcsClient {

	static final String MESSAGE_KEY = "SERVER_MESSAGE";
	Logger logger = Logger.getLogger("SmackCcsClient");

	public static final String GCM_SERVER = "gcm.googleapis.com";
	public static final int GCM_PORT = 5235;

	public static final String GCM_ELEMENT_NAME = "gcm";
	public static final String GCM_NAMESPACE = "google:mobile:data";

	static Random random = new Random();
	XMPPConnection connection;
	ConnectionConfiguration config;

	/**
	 * XMPP Packet Extension for GCM Cloud Connection Server.
	 */
	class GcmPacketExtension extends DefaultPacketExtension {
		String json;

		public GcmPacketExtension(String json) {
			super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
			this.json = json;
		}

		public String getJson() {
			return json;
		}

		@Override
		public String toXML() {
			return String.format("<%s xmlns=\"%s\">%s", GCM_ELEMENT_NAME,
					GCM_NAMESPACE, json, GCM_ELEMENT_NAME);
		}

		@SuppressWarnings("unused")
		public Packet toPacket() {
			return new Message() {
				// Must override toXML() because it includes a 
				@Override
				public String toXML() {

					StringBuilder buf = new StringBuilder();
					buf.append("");
					buf.append(GcmPacketExtension.this.toXML());
					buf.append("");
					return buf.toString();
				}
			};
		}
	}

	public SmackCcsClient() {
		// Add GcmPacketExtension
		ProviderManager.getInstance().addExtensionProvider(GCM_ELEMENT_NAME,
				GCM_NAMESPACE, new PacketExtensionProvider() {

					@Override
					public PacketExtension parseExtension(XmlPullParser parser)
							throws Exception {
						String json = parser.nextText();
						GcmPacketExtension packet = new GcmPacketExtension(json);
						return packet;
					}
				});
	}

	/**
	 * Returns a random message id to uniquely identify a message.
	 *
	 * Note: This is generated by a pseudo random number generator for
	 * illustration purpose, and is not guaranteed to be unique.
	 *
	 */
	public String getRandomMessageId() {
		return "m-" + Long.toString(random.nextLong());
	}

	/**
	 * Sends a downstream GCM message.
	 */
	public void send(String jsonRequest) {
		Packet request = new GcmPacketExtension(jsonRequest).toPacket();
		connection.sendPacket(request);
	}

	/**
	 * Handles an upstream data message from a device application.
	 *
	 * This sample echo server sends an echo message back to the device.
	 * Subclasses should override this method to process an upstream message.
	 */
	public void handleIncomingDataMessage(Map jsonObject) {
		
		String from = jsonObject.get("from").toString();

		// PackageName of the application that sent this message.
		String category = jsonObject.get("category").toString();

		// Use the packageName as the collapseKey in the echo packet
		String collapseKey = "echo:CollapseKey";
		@SuppressWarnings("unchecked")
		Map payload = (Map) jsonObject
				.get("data");

		String action = payload.get("ACTION");

		if ("ECHO".equals(action)) {

			String clientMessage = payload.get("CLIENT_MESSAGE");
			payload.put(MESSAGE_KEY, "ECHO: " + clientMessage);

			// Send an ECHO response back
			String echo = createJsonMessage(from, getRandomMessageId(),
					payload, collapseKey, null, false);
			send(echo);
		} else if ("REGISTER".equals(action)) {
			try {
				RegIdManager.writeToFile(from);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	/**
	 * Handles an ACK.
	 *
	 * By default, it only logs a INFO message, but subclasses could override it
	 * to properly handle ACKS.
	 */
	public void handleAckReceipt(Map jsonObject) {
		String messageId = jsonObject.get("message_id").toString();
		String from = jsonObject.get("from").toString();
		logger.log(Level.INFO, "handleAckReceipt() from: " + from
				+ ", messageId: " + messageId);
	}

	/**
	 * Handles a NACK.
	 *
	 * By default, it only logs a INFO message, but subclasses could override it
	 * to properly handle NACKS.
	 */
	public void handleNackReceipt(Map jsonObject) {
		String messageId = jsonObject.get("message_id").toString();
		String from = jsonObject.get("from").toString();
		logger.log(Level.INFO, "handleNackReceipt() from: " + from
				+ ", messageId: " + messageId);
	}

	/**
	 * Creates a JSON encoded GCM message.
	 *
	 * @param to
	 *            RegistrationId of the target device (Required).
	 * @param messageId
	 *            Unique messageId for which CCS will send an "ack/nack"
	 *            (Required).
	 * @param payload
	 *            Message content intended for the application. (Optional).
	 * @param collapseKey
	 *            GCM collapse_key parameter (Optional).
	 * @param timeToLive
	 *            GCM time_to_live parameter (Optional).
	 * @param delayWhileIdle
	 *            GCM delay_while_idle parameter (Optional).
	 * @return JSON encoded GCM message.
	 */
	public static String createJsonMessage(String to, String messageId,
			Map payload, String collapseKey, Long timeToLive,
			Boolean delayWhileIdle) {
		Map message = new HashMap();
		message.put("to", to);
		if (collapseKey != null) {
			message.put("collapse_key", collapseKey);
		}
		if (timeToLive != null) {
			message.put("time_to_live", timeToLive);
		}
		if (delayWhileIdle != null && delayWhileIdle) {
			message.put("delay_while_idle", true);
		}
		message.put("message_id", messageId);
		message.put("data", payload);
		return JSONValue.toJSONString(message);
	}

	/**
	 * Creates a JSON encoded ACK message for an upstream message received from
	 * an application.
	 *
	 * @param to
	 *            RegistrationId of the device who sent the upstream message.
	 * @param messageId
	 *            messageId of the upstream message to be acknowledged to CCS.
	 * @return JSON encoded ack.
	 */
	public static String createJsonAck(String to, String messageId) {
		Map message = new HashMap();
		message.put("message_type", "ack");
		message.put("to", to);
		message.put("message_id", messageId);
		return JSONValue.toJSONString(message);
	}

	/**
	 * Connects to GCM Cloud Connection Server using the supplied credentials.
	 *
	 * @param username
	 *            GCM_SENDER_ID@gcm.googleapis.com
	 * @param password
	 *            API Key
	 * @throws XMPPException
	 */
	public void connect(String username, String password) throws XMPPException {
		config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
		config.setSecurityMode(SecurityMode.enabled);
		config.setReconnectionAllowed(true);
		config.setRosterLoadedAtLogin(false);
		config.setSendPresence(false);
		config.setSocketFactory(SSLSocketFactory.getDefault());

		// NOTE: Set to true to launch a window with information about packets
		// sent and received
		config.setDebuggerEnabled(true);

		// -Dsmack.debugEnabled=true
		XMPPConnection.DEBUG_ENABLED = true;

		connection = new XMPPConnection(config);
		connection.connect();

		connection.addConnectionListener(new ConnectionListener() {

			@Override
			public void reconnectionSuccessful() {
				logger.info("Reconnecting..");
			}

			@Override
			public void reconnectionFailed(Exception e) {
				logger.log(Level.INFO, "Reconnection failed.. ", e);
			}

			@Override
			public void reconnectingIn(int seconds) {
				logger.log(Level.INFO, "Reconnecting in %d secs", seconds);
			}

			@Override
			public void connectionClosedOnError(Exception e) {
				logger.log(Level.INFO, "Connection closed on error.");
			}

			@Override
			public void connectionClosed() {
				logger.info("Connection closed.");
			}
		});

		// Handle incoming packets
		connection.addPacketListener(new PacketListener() {

			@Override
			public void processPacket(Packet packet) {
				logger.log(Level.INFO, "Received: " + packet.toXML());
				Message incomingMessage = (Message) packet;
				GcmPacketExtension gcmPacket = (GcmPacketExtension) incomingMessage
						.getExtension(GCM_NAMESPACE);
				String json = gcmPacket.getJson();
				try {
					@SuppressWarnings("unchecked")
					Map jsonObject = (Map) JSONValue
							.parseWithException(json);

					// present for "ack"/"nack", null otherwise
					Object messageType = jsonObject.get("message_type");

					if (messageType == null) {
						// Normal upstream data message
						handleIncomingDataMessage(jsonObject);

						// Send ACK to CCS
						String messageId = jsonObject.get("message_id")
								.toString();
						String from = jsonObject.get("from").toString();
						String ack = createJsonAck(from, messageId);
						send(ack);
					} else if ("ack".equals(messageType.toString())) {
						// Process Ack
						handleAckReceipt(jsonObject);
					} else if ("nack".equals(messageType.toString())) {
						// Process Nack
						handleNackReceipt(jsonObject);
					} else {
						logger.log(Level.WARNING,
								"Unrecognized message type (%s)",
								messageType.toString());
					}
				} catch (ParseException e) {
					logger.log(Level.SEVERE, "Error parsing JSON " + json, e);
				} catch (Exception e) {
					logger.log(Level.SEVERE, "Couldn't send echo.", e);
				}
			}
		}, new PacketTypeFilter(Message.class));

		// Log all outgoing packets
		connection.addPacketInterceptor(new PacketInterceptor() {
			@Override
			public void interceptPacket(Packet packet) {
				logger.log(Level.INFO, "Sent: {0}", packet.toXML());
			}
		}, new PacketTypeFilter(Message.class));

		connection.login(username, password);
	}

	public static void sendMessage(String userName,
			final String GOOGLE_SERVER_KEY, String toDeviceRegId, String message) {

		SmackCcsClient ccsClient = new SmackCcsClient();

		try {
			ccsClient.connect(userName, GOOGLE_SERVER_KEY);
		} catch (XMPPException e) {
			e.printStackTrace();
		}

		String messageId = ccsClient.getRandomMessageId();
		Map payload = new HashMap();
		payload.put(MESSAGE_KEY, message);
		payload.put("EmbeddedMessageId", messageId);
		String collapseKey = "sample";
		Long timeToLive = 10000L;
		Boolean delayWhileIdle = true;
		ccsClient.send(createJsonMessage(toDeviceRegId, messageId, payload,
				collapseKey, timeToLive, delayWhileIdle));
	}
}

GCMNotification.java

package com.javapapers.java.gcm;

import java.io.IOException;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@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 = "AIzaSyA9DQTcggUtABVC9lnV_Xb5VEQ8iKBEaP4";

	// Put your Google Project number here
	final String GOOGLE_USERNAME = "512212818580" + "@gcm.googleapis.com";

	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 {

		try {
			String userMessage = request.getParameter("message");
			Set regIdSet = RegIdManager.readFromFile();
			String toDeviceRegId = (String) (regIdSet.toArray())[0];
			SmackCcsClient.sendMessage(GOOGLE_USERNAME, GOOGLE_SERVER_KEY,
					toDeviceRegId,userMessage);
			request.setAttribute("pushStatus", "Message Sent.");
		} 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);
	}

}

RegIdManager.java

package com.javapapers.java.gcm;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Set;

public class RegIdManager {
	static final String REG_ID_STORE = "GCMRegId.txt";
	public static void writeToFile(String regId) throws IOException {
		Set regIdSet = readFromFile();

		if (!regIdSet.contains(regId)) {
			PrintWriter out = new PrintWriter(new BufferedWriter(
					new FileWriter(REG_ID_STORE, true)));
			out.println(regId);
			out.close();
		}
	}

	public static Set readFromFile() throws IOException {
		BufferedReader br = new BufferedReader(new FileReader(REG_ID_STORE));
		String regId = "";
		Set regIdSet = new HashSet();
		while ((regId = br.readLine()) != null) {
			regIdSet.add(regId);
		}
		br.close();
		return regIdSet;
	}
}

Google-GCM-XMMP-Server

Google GCM Client

The Android Google GCM Client application is no different from previous HTTP based clients. I have made some minor adjustments in order to incorporate the example communication flow.

RegisterActivity.java

package com.javapapers.android.gcm.multiple;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;

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.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 btnXmppRegiser;
	Button btnSendMessage;
	GoogleCloudMessaging gcm;
	Context context;
	String regId;
	AsyncTask sendTask;
	AtomicInteger ccsMsgId = new AtomicInteger();

	static final String GOOGLE_PROJECT_ID = "512212818580";
	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();
				}
			}
		});

		btnXmppRegiser = (Button) findViewById(R.id.btnXmppRegiser);
		btnXmppRegiser.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 {

					sendMessage("REGISTER");
				}
			}
		});

		btnSendMessage = (Button) findViewById(R.id.btnSendMessage);
		btnSendMessage.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 {

					sendMessage("ECHO");
				}
			}
		});
	}

	private void sendMessage(final String action) {

		sendTask = new AsyncTask() {
			@Override
			protected String doInBackground(Void... params) {

				Bundle data = new Bundle();
				data.putString("ACTION", action);
				data.putString("CLIENT_MESSAGE", "Hello GCM CCS XMPP!");
				String id = Integer.toString(ccsMsgId.incrementAndGet());

				try {
					Log.d("RegisterActivity", "messageid: " + id);
					gcm.send(GOOGLE_PROJECT_ID + "@gcm.googleapis.com", id,
							data);
					Log.d("RegisterActivity", "After gcm.send successful.");
				} catch (IOException e) {
					Log.d("RegisterActivity", "Exception: " + e);
					e.printStackTrace();
				}
				return "Sent message.";
			}

			@Override
			protected void onPostExecute(String result) {
				sendTask = null;
				Toast.makeText(getApplicationContext(), result,
						Toast.LENGTH_LONG).show();
			}

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

	}

	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(
				RegisterActivity.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(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(
				RegisterActivity.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();
	}
}

GCMNotificationIntentService.java

package com.javapapers.android.gcm.multiple;

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.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)) {

				sendNotification("SERVER_MESSAGE: " + extras.get("SERVER_MESSAGE"));
				Log.i(TAG, "SERVER_MESSAGE: " + 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, RegisterActivity.class), 0);

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

		mBuilder.setContentIntent(contentIntent);
		mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
		Log.d(TAG, "Notification sent successfully.");
	}
}

GcmBroadcastReceiver.java

package com.javapapers.android.gcm.multiple;

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);
	}
}

Google GCM XMPP Client

Google GCM CCS XMMP Downstream Message with Echo

Google GCM CCS XMMP Downstream Message

This Android tutorial was added on 22/05/2014.

Comments on "Google Cloud Messaging GCM CCS with XMPP" Tutorial:

  1. Ousama Esbel says:

    Awesome!! I have been waiting for this tutorial for so long. Thanks for giving the time to explain and do the tutorials. You are a a legend.

  2. Joe says:

    Thanks Ousama.

    Presently I am working on a chat application with XMPP and will post it in a couple of days and that will be interesting too.

  3. Pratik says:

    Thanks joe

  4. Anonymous says:

    Hi Joe, when will you publish the chat application with XMPP? Eagerly waiting for it

  5. q2w3e says:

    Hi, thanks for the tutorial. i’m new in programming… I can’t run the server. What should I do?

  6. Joe says:

    I will surely publish it next week :-)

  7. James says:

    Hey Joe,

    I wanted to ask a question about the server application. You’re java application SmackCcsClient.java is nearly identical to the Google provided version on develop.android.com, but you don’t have a main. You instead use sendMessage() which is called by GCMNotification.java. How does the program run without a main? I can’t compile it in eclipse.

    Is there something I’m missing?

  8. adiek says:

    Great tutorial..

    Hi joe,
    I interest to make my App use this tutorial, i’ve created chat app using GCM only,sometimes i can send message from android device to another android device,but sometimes failed. till now i dont understand with my App why messages cant received..
    I learn to make chat app and last time i read your article about this, i want to ask.

    1. if users active more than 1 Million? is possible make chat app just use GCM?
    2. are other chat app (whatsapp,line, etc) use xmpp & ccs ?

    thanks so much joe..
    I bookmark this link to guide my app to be better.

  9. nha says:

    can you help me fix this bug? I implement follow this tutorial, but i met this bug.

    …………………………

    type Exception report

    message Servlet execution threw an exception

    description The server encountered an internal error that prevented it from fulfilling this request.

    exception

    javax.servlet.ServletException: Servlet execution threw an exception
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

    root cause

    java.lang.ExceptionInInitializerError
    org.jivesoftware.smack.ConnectionConfiguration.(ConnectionConfiguration.java:66)
    com.tunha.gcm.SmackCcsClient.connect(SmackCcsClient.java:193)
    com.tunha.gcm.GCMNotification.doPost(GCMNotification.java:48)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

    root cause

    java.lang.IllegalStateException: org.xmlpull.v1.XmlPullParserException: caused by: org.xmlpull.v1.XmlPullParserException: resource not found: /META-INF/services/org.xmlpull.v1.XmlPullParserFactory make sure that parser implementing XmlPull API is available
    org.jivesoftware.smack.SmackConfiguration.(SmackConfiguration.java:158)
    org.jivesoftware.smack.ConnectionConfiguration.(ConnectionConfiguration.java:66)
    com.tunha.gcm.SmackCcsClient.connect(SmackCcsClient.java:193)
    com.tunha.gcm.GCMNotification.doPost(GCMNotification.java:48)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

    root cause

    org.xmlpull.v1.XmlPullParserException: caused by: org.xmlpull.v1.XmlPullParserException: resource not found: /META-INF/services/org.xmlpull.v1.XmlPullParserFactory make sure that parser implementing XmlPull API is available
    org.xmlpull.v1.XmlPullParserFactory.newInstance(XmlPullParserFactory.java:294)
    org.xmlpull.v1.XmlPullParserFactory.newInstance(XmlPullParserFactory.java:259)
    org.jivesoftware.smack.SmackConfiguration.processConfigFile(SmackConfiguration.java:352)
    org.jivesoftware.smack.SmackConfiguration.processConfigFile(SmackConfiguration.java:347)
    org.jivesoftware.smack.SmackConfiguration.(SmackConfiguration.java:155)
    org.jivesoftware.smack.ConnectionConfiguration.(ConnectionConfiguration.java:66)
    com.tunha.gcm.SmackCcsClient.connect(SmackCcsClient.java:193)
    com.tunha.gcm.GCMNotification.doPost(GCMNotification.java:48)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

  10. Tien says:

    Thanks verymuch. I download them then import and run but why it is not as you demo. Message type is always send_event
    I need for your help

  11. Ashish Srivastava says:

    Hey Joe,
    Thanks for tutorial!

    I have a query for follwing :

    Google CCS XMMP Upstream Messaging Prerequisites

    1.Project in Google Developers Console
    2.Server API Key
    3.Google Approval for API access

    My question is “What is step 3 signifying here?” Which API access you are talking about for Google approval?

  12. Santhanam gunasekaran says:

    Hi joe,

    Thanks for the tutorial,

    This blog is only contains latest tutorial in internet for android chat app, thank for this additionally.

    i can understand the coding flow in client and XMPP server part, but i dont have any idea to Make XMPP server.

    whether i want to run SmackClass in comment prompt as you mentioned (if yes i’m getting no class exception for configuration) or can i keep any apache tomcat server. for future i want to customize server code for user online status options.

    Pls give your reply ASAP.

  13. bhavani shankar says:

    Hi Joe,
    Thanks for the tutorial!!
    I have a query.. Can we have a java program which calls GCM server which executes on a local machine rather than having a web server. The java program is designed so that it calls GCM server with regid and apikey to send messages. If possible can you share idea please..

    Thank You.

  14. Okey says:

    Thanks for the beautiful tutorial.
    Pls how do I send notification base on geofence?

  15. nawaz says:

    how i add smack lib to my project and how cloud do connection with server side php pls reply

  16. Sean Calderon says:

    Hi Joe,

    I implemented your first GCM HTTP Push Notification tutorial successfully, and now I’m attempting to implement the XMPP Server. I’m having issues, however, importing the project into eclipse. I just downloaded the most recent version of Eclipse (IDE for Java Developers, Mars Release 4.5.0) and imported the project as a java project. I’m showing 19 errors, mostly of which are related to the “HttpServlet” and “HttpServletRequest” and the JSON values cannot be resolved. Does this need to be imported as a Java Web app. Any insight you can provide would be greatly appreciated.

    Thanks!

  17. Aman says:

    Please help i am unable to code using xmpp

  18. chor sophea says:

    Hello sir. The server is good but i want to do implementation with php . Could you post the code with php ?

Comments are closed for this "Google Cloud Messaging GCM CCS with XMPP" tutorial.