Loading docs/html/google/gcm/ccs.jd +320 −292 Original line number Diff line number Diff line Loading @@ -535,6 +535,8 @@ 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.SmackException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.filter.PacketTypeFilter; Loading @@ -544,122 +546,87 @@ 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.tcp.XMPPTCPConnection; 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.UUID; 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. * Sample Smack implementation of a client for GCM Cloud Connection Server. This * code can be run as a standalone CCS client. * * <p>For illustration purposes only. */ public class SmackCcsClient { Logger logger = Logger.getLogger("SmackCcsClient"); public static final String GCM_SERVER = "gcm.googleapis.com"; public static final int GCM_PORT = 5235; private static final Logger logger = Logger.getLogger("SmackCcsClient"); public static final String GCM_ELEMENT_NAME = "gcm"; public static final String GCM_NAMESPACE = "google:mobile:data"; private static final String GCM_SERVER = "gcm.googleapis.com"; private static final int GCM_PORT = 5235; static Random random = new Random(); XMPPConnection connection; ConnectionConfiguration config; private static final String GCM_ELEMENT_NAME = "gcm"; private static final String GCM_NAMESPACE = "google:mobile:data"; /** * 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; } static { ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE, new PacketExtensionProvider() { @Override public String toXML() { return String.format("<%s xmlns=\"%s\">%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 <body> @Override public String toXML() { StringBuilder buf = new StringBuilder(); buf.append("<message"); if (getXmlns() != null) { buf.append(" xmlns=\"").append(getXmlns()).append("\""); } if (getLanguage() != null) { buf.append(" xml:lang=\"").append(getLanguage()).append("\""); } if (getPacketID() != null) { buf.append(" id=\"").append(getPacketID()).append("\""); } if (getTo() != null) { buf.append(" to=\"").append(StringUtils.escapeForXML(getTo())).append("\""); } if (getFrom() != null) { buf.append(" from=\"").append(StringUtils.escapeForXML(getFrom())).append("\""); } buf.append(">"); buf.append(GcmPacketExtension.this.toXML()); buf.append("</message>"); return buf.toString(); } }; public PacketExtension parseExtension(XmlPullParser parser) throws Exception { String json = parser.nextText(); return new GcmPacketExtension(json); } }); } public SmackCcsClient() { // Add GcmPacketExtension ProviderManager.getInstance().addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE, new PacketExtensionProvider() { private XMPPConnection connection; @Override public PacketExtension parseExtension(XmlPullParser parser) throws Exception { String json = parser.nextText(); GcmPacketExtension packet = new GcmPacketExtension(json); return packet; /** * Indicates whether the connection is in draining state, which means that it * will not accept any new downstream messages. */ protected volatile boolean connectionDraining = false; /** * Sends a downstream message to GCM. * * @return true if the message has been successfully sent. */ public boolean sendDownstreamMessage(String jsonRequest) throws NotConnectedException { if (!connectionDraining) { send(jsonRequest); return true; } }); logger.info("Dropping downstream message since the connection is draining"); return false; } /** * Returns a random message id to uniquely identify a message. * * <p>Note: * This is generated by a pseudo random number generator for illustration purpose, * and is not guaranteed to be unique. * * <p>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()); public String nextMessageId() { return "m-" + UUID.randomUUID().toString(); } /** * Sends a downstream GCM message. * Sends a packet with contents provided. */ public void send(String jsonRequest) { protected void send(String jsonRequest) throws NotConnectedException { Packet request = new GcmPacketExtension(jsonRequest).toPacket(); connection.sendPacket(request); } Loading @@ -668,62 +635,81 @@ public class SmackCcsClient { * Handles an upstream data message from a device application. * * <p>This sample echo server sends an echo message back to the device. * Subclasses should override this method to process an upstream message. * Subclasses should override this method to properly process upstream messages. */ public void handleIncomingDataMessage(Map<String, Object> jsonObject) { String from = jsonObject.get("from").toString(); protected void handleUpstreamMessage(Map<String, Object> jsonObject) { // 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"; String category = (String) jsonObject.get("category"); String from = (String) jsonObject.get("from"); @SuppressWarnings("unchecked") Map<String, String> payload = (Map<String, String>) jsonObject.get("data"); payload.put("ECHO", "Application: " + category); // Send an ECHO response back String echo = createJsonMessage(from, getRandomMessageId(), payload, collapseKey, null, false); send(echo); String echo = createJsonMessage(from, nextMessageId(), payload, "echo:CollapseKey", null, false); try { sendDownstreamMessage(echo); } catch (NotConnectedException e) { logger.log(Level.WARNING, "Not connected anymore, echo message is not sent", e); } } /** * Handles an ACK. * * <p>By default, it only logs a {@code INFO} message, but subclasses could override it to * properly handle ACKS. * <p>Logs a {@code INFO} message, but subclasses could override it to * properly handle ACKs. */ public void handleAckReceipt(Map<String, Object> jsonObject) { String messageId = jsonObject.get("message_id").toString(); String from = jsonObject.get("from").toString(); logger.log(Level.INFO, "handleAckReceipt() from: " + from + ", messageId: " + messageId); protected void handleAckReceipt(Map<String, Object> jsonObject) { String messageId = (String) jsonObject.get("message_id"); String from = (String) jsonObject.get("from"); logger.log(Level.INFO, "handleAckReceipt() from: " + from + ", messageId: " + messageId); } /** * Handles a NACK. * * <p>By default, it only logs a {@code INFO} message, but subclasses could override it to * properly handle NACKS. * <p>Logs a {@code INFO} message, but subclasses could override it to * properly handle NACKs. */ public void handleNackReceipt(Map<String, Object> jsonObject) { String messageId = jsonObject.get("message_id").toString(); String from = jsonObject.get("from").toString(); logger.log(Level.INFO, "handleNackReceipt() from: " + from + ", messageId: " + messageId); protected void handleNackReceipt(Map<String, Object> jsonObject) { String messageId = (String) jsonObject.get("message_id"); String from = (String) jsonObject.get("from"); logger.log(Level.INFO, "handleNackReceipt() from: " + from + ", messageId: " + messageId); } protected void handleControlMessage(Map<String, Object> jsonObject) { logger.log(Level.INFO, "handleControlMessage(): " + jsonObject); String controlType = (String) jsonObject.get("control_type"); if ("CONNECTION_DRAINING".equals(controlType)) { connectionDraining = true; } else { logger.log(Level.INFO, "Unrecognized control type: %s. This could happen if new features are " + "added to the CCS protocol.", controlType); } } /** * 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 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<String, String> payload, String collapseKey, Long timeToLive, Boolean delayWhileIdle) { public static String createJsonMessage(String to, String messageId, Map<String, String> payload, String collapseKey, Long timeToLive, Boolean delayWhileIdle) { Map<String, Object> message = new HashMap<String, Object>(); message.put("to", to); if (collapseKey != null) { Loading @@ -741,13 +727,14 @@ public class SmackCcsClient { } /** * Creates a JSON encoded ACK message for an upstream message received from an application. * 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) { protected static String createJsonAck(String to, String messageId) { Map<String, Object> message = new HashMap<String, Object>(); message.put("message_type", "ack"); message.put("to", to); Loading @@ -758,54 +745,23 @@ public class SmackCcsClient { /** * Connects to GCM Cloud Connection Server using the supplied credentials. * * @param username GCM_SENDER_ID@gcm.googleapis.com * @param password API Key * @throws XMPPException * @param senderId Your GCM project number * @param apiKey API Key of your project */ public void connect(String username, String password) throws XMPPException { config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT); public void connect(long senderId, String apiKey) throws XMPPException, IOException, SmackException { ConnectionConfiguration 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 = new XMPPTCPConnection(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."); } }); connection.addConnectionListener(new LoggingConnectionListener()); // Handle incoming packets connection.addPacketListener(new PacketListener() { Loading @@ -815,23 +771,25 @@ public class SmackCcsClient { logger.log(Level.INFO, "Received: " + packet.toXML()); Message incomingMessage = (Message) packet; GcmPacketExtension gcmPacket = (GcmPacketExtension) incomingMessage.getExtension(GCM_NAMESPACE); (GcmPacketExtension) incomingMessage. getExtension(GCM_NAMESPACE); String json = gcmPacket.getJson(); try { @SuppressWarnings("unchecked") Map<String, Object> jsonObject = (Map<String, Object>) JSONValue.parseWithException(json); (Map<String, Object>) JSONValue. parseWithException(json); // present for "ack"/"nack", null otherwise Object messageType = jsonObject.get("message_type"); if (messageType == null) { // Normal upstream data message handleIncomingDataMessage(jsonObject); handleUpstreamMessage(jsonObject); // Send ACK to CCS String messageId = jsonObject.get("message_id").toString(); String from = jsonObject.get("from").toString(); String messageId = (String) jsonObject.get("message_id"); String from = (String) jsonObject.get("from"); String ack = createJsonAck(from, messageId); send(ack); } else if ("ack".equals(messageType.toString())) { Loading @@ -840,19 +798,22 @@ public class SmackCcsClient { } else if ("nack".equals(messageType.toString())) { // Process Nack handleNackReceipt(jsonObject); } else if ("control".equals(messageType.toString())) { // Process control message handleControlMessage(jsonObject); } else { logger.log(Level.WARNING, "Unrecognized message type (%s)", 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); logger.log(Level.SEVERE, "Failed to process packet", e); } } }, new PacketTypeFilter(Message.class)); // Log all outgoing packets connection.addPacketInterceptor(new PacketInterceptor() { @Override Loading @@ -861,35 +822,102 @@ public class SmackCcsClient { } }, new PacketTypeFilter(Message.class)); connection.login(username, password); connection.login(senderId + "@gcm.googleapis.com", apiKey); } public static void main(String [] args) { final String userName = "Your GCM Sender Id" + "@gcm.googleapis.com"; final String password = "API Key"; public static void main(String[] args) throws Exception { final long senderId = 1234567890L; // your GCM sender id final String password = "Your API key"; SmackCcsClient ccsClient = new SmackCcsClient(); try { ccsClient.connect(userName, password); } catch (XMPPException e) { e.printStackTrace(); } ccsClient.connect(senderId, password); // Send a sample hello downstream message to a device. String toRegId = "RegistrationIdOfTheTargetDevice"; String messageId = ccsClient.getRandomMessageId(); String messageId = ccsClient.nextMessageId(); Map<String, String> payload = new HashMap<String, String>(); payload.put("Hello", "World"); payload.put("CCS", "Dummy Message"); payload.put("EmbeddedMessageId", messageId); String collapseKey = "sample"; Long timeToLive = 10000L; Boolean delayWhileIdle = true; ccsClient.send(createJsonMessage(toRegId, messageId, payload, collapseKey, timeToLive, delayWhileIdle)); String message = createJsonMessage(toRegId, messageId, payload, collapseKey, timeToLive, true); ccsClient.sendDownstreamMessage(message); } /** * XMPP Packet Extension for GCM Cloud Connection Server. */ private static final class GcmPacketExtension extends DefaultPacketExtension { private final 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</%s>", GCM_ELEMENT_NAME, GCM_NAMESPACE, StringUtils.escapeForXML(json), GCM_ELEMENT_NAME); } public Packet toPacket() { Message message = new Message(); message.addExtension(this); return message; } } private static final class LoggingConnectionListener implements ConnectionListener { @Override public void connected(XMPPConnection xmppConnection) { logger.info("Connected."); } @Override public void authenticated(XMPPConnection xmppConnection) { logger.info("Authenticated."); } @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.info("Connection closed on error."); } @Override public void connectionClosed() { logger.info("Connection closed."); } } }</pre> <h3 id="python">Python sample</h3> <p>Here is an example of a CCS app server written in Python. This sample echo Loading Loading
docs/html/google/gcm/ccs.jd +320 −292 Original line number Diff line number Diff line Loading @@ -535,6 +535,8 @@ 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.SmackException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.filter.PacketTypeFilter; Loading @@ -544,122 +546,87 @@ 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.tcp.XMPPTCPConnection; 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.UUID; 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. * Sample Smack implementation of a client for GCM Cloud Connection Server. This * code can be run as a standalone CCS client. * * <p>For illustration purposes only. */ public class SmackCcsClient { Logger logger = Logger.getLogger("SmackCcsClient"); public static final String GCM_SERVER = "gcm.googleapis.com"; public static final int GCM_PORT = 5235; private static final Logger logger = Logger.getLogger("SmackCcsClient"); public static final String GCM_ELEMENT_NAME = "gcm"; public static final String GCM_NAMESPACE = "google:mobile:data"; private static final String GCM_SERVER = "gcm.googleapis.com"; private static final int GCM_PORT = 5235; static Random random = new Random(); XMPPConnection connection; ConnectionConfiguration config; private static final String GCM_ELEMENT_NAME = "gcm"; private static final String GCM_NAMESPACE = "google:mobile:data"; /** * 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; } static { ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE, new PacketExtensionProvider() { @Override public String toXML() { return String.format("<%s xmlns=\"%s\">%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 <body> @Override public String toXML() { StringBuilder buf = new StringBuilder(); buf.append("<message"); if (getXmlns() != null) { buf.append(" xmlns=\"").append(getXmlns()).append("\""); } if (getLanguage() != null) { buf.append(" xml:lang=\"").append(getLanguage()).append("\""); } if (getPacketID() != null) { buf.append(" id=\"").append(getPacketID()).append("\""); } if (getTo() != null) { buf.append(" to=\"").append(StringUtils.escapeForXML(getTo())).append("\""); } if (getFrom() != null) { buf.append(" from=\"").append(StringUtils.escapeForXML(getFrom())).append("\""); } buf.append(">"); buf.append(GcmPacketExtension.this.toXML()); buf.append("</message>"); return buf.toString(); } }; public PacketExtension parseExtension(XmlPullParser parser) throws Exception { String json = parser.nextText(); return new GcmPacketExtension(json); } }); } public SmackCcsClient() { // Add GcmPacketExtension ProviderManager.getInstance().addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE, new PacketExtensionProvider() { private XMPPConnection connection; @Override public PacketExtension parseExtension(XmlPullParser parser) throws Exception { String json = parser.nextText(); GcmPacketExtension packet = new GcmPacketExtension(json); return packet; /** * Indicates whether the connection is in draining state, which means that it * will not accept any new downstream messages. */ protected volatile boolean connectionDraining = false; /** * Sends a downstream message to GCM. * * @return true if the message has been successfully sent. */ public boolean sendDownstreamMessage(String jsonRequest) throws NotConnectedException { if (!connectionDraining) { send(jsonRequest); return true; } }); logger.info("Dropping downstream message since the connection is draining"); return false; } /** * Returns a random message id to uniquely identify a message. * * <p>Note: * This is generated by a pseudo random number generator for illustration purpose, * and is not guaranteed to be unique. * * <p>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()); public String nextMessageId() { return "m-" + UUID.randomUUID().toString(); } /** * Sends a downstream GCM message. * Sends a packet with contents provided. */ public void send(String jsonRequest) { protected void send(String jsonRequest) throws NotConnectedException { Packet request = new GcmPacketExtension(jsonRequest).toPacket(); connection.sendPacket(request); } Loading @@ -668,62 +635,81 @@ public class SmackCcsClient { * Handles an upstream data message from a device application. * * <p>This sample echo server sends an echo message back to the device. * Subclasses should override this method to process an upstream message. * Subclasses should override this method to properly process upstream messages. */ public void handleIncomingDataMessage(Map<String, Object> jsonObject) { String from = jsonObject.get("from").toString(); protected void handleUpstreamMessage(Map<String, Object> jsonObject) { // 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"; String category = (String) jsonObject.get("category"); String from = (String) jsonObject.get("from"); @SuppressWarnings("unchecked") Map<String, String> payload = (Map<String, String>) jsonObject.get("data"); payload.put("ECHO", "Application: " + category); // Send an ECHO response back String echo = createJsonMessage(from, getRandomMessageId(), payload, collapseKey, null, false); send(echo); String echo = createJsonMessage(from, nextMessageId(), payload, "echo:CollapseKey", null, false); try { sendDownstreamMessage(echo); } catch (NotConnectedException e) { logger.log(Level.WARNING, "Not connected anymore, echo message is not sent", e); } } /** * Handles an ACK. * * <p>By default, it only logs a {@code INFO} message, but subclasses could override it to * properly handle ACKS. * <p>Logs a {@code INFO} message, but subclasses could override it to * properly handle ACKs. */ public void handleAckReceipt(Map<String, Object> jsonObject) { String messageId = jsonObject.get("message_id").toString(); String from = jsonObject.get("from").toString(); logger.log(Level.INFO, "handleAckReceipt() from: " + from + ", messageId: " + messageId); protected void handleAckReceipt(Map<String, Object> jsonObject) { String messageId = (String) jsonObject.get("message_id"); String from = (String) jsonObject.get("from"); logger.log(Level.INFO, "handleAckReceipt() from: " + from + ", messageId: " + messageId); } /** * Handles a NACK. * * <p>By default, it only logs a {@code INFO} message, but subclasses could override it to * properly handle NACKS. * <p>Logs a {@code INFO} message, but subclasses could override it to * properly handle NACKs. */ public void handleNackReceipt(Map<String, Object> jsonObject) { String messageId = jsonObject.get("message_id").toString(); String from = jsonObject.get("from").toString(); logger.log(Level.INFO, "handleNackReceipt() from: " + from + ", messageId: " + messageId); protected void handleNackReceipt(Map<String, Object> jsonObject) { String messageId = (String) jsonObject.get("message_id"); String from = (String) jsonObject.get("from"); logger.log(Level.INFO, "handleNackReceipt() from: " + from + ", messageId: " + messageId); } protected void handleControlMessage(Map<String, Object> jsonObject) { logger.log(Level.INFO, "handleControlMessage(): " + jsonObject); String controlType = (String) jsonObject.get("control_type"); if ("CONNECTION_DRAINING".equals(controlType)) { connectionDraining = true; } else { logger.log(Level.INFO, "Unrecognized control type: %s. This could happen if new features are " + "added to the CCS protocol.", controlType); } } /** * 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 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<String, String> payload, String collapseKey, Long timeToLive, Boolean delayWhileIdle) { public static String createJsonMessage(String to, String messageId, Map<String, String> payload, String collapseKey, Long timeToLive, Boolean delayWhileIdle) { Map<String, Object> message = new HashMap<String, Object>(); message.put("to", to); if (collapseKey != null) { Loading @@ -741,13 +727,14 @@ public class SmackCcsClient { } /** * Creates a JSON encoded ACK message for an upstream message received from an application. * 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) { protected static String createJsonAck(String to, String messageId) { Map<String, Object> message = new HashMap<String, Object>(); message.put("message_type", "ack"); message.put("to", to); Loading @@ -758,54 +745,23 @@ public class SmackCcsClient { /** * Connects to GCM Cloud Connection Server using the supplied credentials. * * @param username GCM_SENDER_ID@gcm.googleapis.com * @param password API Key * @throws XMPPException * @param senderId Your GCM project number * @param apiKey API Key of your project */ public void connect(String username, String password) throws XMPPException { config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT); public void connect(long senderId, String apiKey) throws XMPPException, IOException, SmackException { ConnectionConfiguration 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 = new XMPPTCPConnection(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."); } }); connection.addConnectionListener(new LoggingConnectionListener()); // Handle incoming packets connection.addPacketListener(new PacketListener() { Loading @@ -815,23 +771,25 @@ public class SmackCcsClient { logger.log(Level.INFO, "Received: " + packet.toXML()); Message incomingMessage = (Message) packet; GcmPacketExtension gcmPacket = (GcmPacketExtension) incomingMessage.getExtension(GCM_NAMESPACE); (GcmPacketExtension) incomingMessage. getExtension(GCM_NAMESPACE); String json = gcmPacket.getJson(); try { @SuppressWarnings("unchecked") Map<String, Object> jsonObject = (Map<String, Object>) JSONValue.parseWithException(json); (Map<String, Object>) JSONValue. parseWithException(json); // present for "ack"/"nack", null otherwise Object messageType = jsonObject.get("message_type"); if (messageType == null) { // Normal upstream data message handleIncomingDataMessage(jsonObject); handleUpstreamMessage(jsonObject); // Send ACK to CCS String messageId = jsonObject.get("message_id").toString(); String from = jsonObject.get("from").toString(); String messageId = (String) jsonObject.get("message_id"); String from = (String) jsonObject.get("from"); String ack = createJsonAck(from, messageId); send(ack); } else if ("ack".equals(messageType.toString())) { Loading @@ -840,19 +798,22 @@ public class SmackCcsClient { } else if ("nack".equals(messageType.toString())) { // Process Nack handleNackReceipt(jsonObject); } else if ("control".equals(messageType.toString())) { // Process control message handleControlMessage(jsonObject); } else { logger.log(Level.WARNING, "Unrecognized message type (%s)", 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); logger.log(Level.SEVERE, "Failed to process packet", e); } } }, new PacketTypeFilter(Message.class)); // Log all outgoing packets connection.addPacketInterceptor(new PacketInterceptor() { @Override Loading @@ -861,35 +822,102 @@ public class SmackCcsClient { } }, new PacketTypeFilter(Message.class)); connection.login(username, password); connection.login(senderId + "@gcm.googleapis.com", apiKey); } public static void main(String [] args) { final String userName = "Your GCM Sender Id" + "@gcm.googleapis.com"; final String password = "API Key"; public static void main(String[] args) throws Exception { final long senderId = 1234567890L; // your GCM sender id final String password = "Your API key"; SmackCcsClient ccsClient = new SmackCcsClient(); try { ccsClient.connect(userName, password); } catch (XMPPException e) { e.printStackTrace(); } ccsClient.connect(senderId, password); // Send a sample hello downstream message to a device. String toRegId = "RegistrationIdOfTheTargetDevice"; String messageId = ccsClient.getRandomMessageId(); String messageId = ccsClient.nextMessageId(); Map<String, String> payload = new HashMap<String, String>(); payload.put("Hello", "World"); payload.put("CCS", "Dummy Message"); payload.put("EmbeddedMessageId", messageId); String collapseKey = "sample"; Long timeToLive = 10000L; Boolean delayWhileIdle = true; ccsClient.send(createJsonMessage(toRegId, messageId, payload, collapseKey, timeToLive, delayWhileIdle)); String message = createJsonMessage(toRegId, messageId, payload, collapseKey, timeToLive, true); ccsClient.sendDownstreamMessage(message); } /** * XMPP Packet Extension for GCM Cloud Connection Server. */ private static final class GcmPacketExtension extends DefaultPacketExtension { private final 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</%s>", GCM_ELEMENT_NAME, GCM_NAMESPACE, StringUtils.escapeForXML(json), GCM_ELEMENT_NAME); } public Packet toPacket() { Message message = new Message(); message.addExtension(this); return message; } } private static final class LoggingConnectionListener implements ConnectionListener { @Override public void connected(XMPPConnection xmppConnection) { logger.info("Connected."); } @Override public void authenticated(XMPPConnection xmppConnection) { logger.info("Authenticated."); } @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.info("Connection closed on error."); } @Override public void connectionClosed() { logger.info("Connection closed."); } } }</pre> <h3 id="python">Python sample</h3> <p>Here is an example of a CCS app server written in Python. This sample echo Loading