Merge "Doc Update: new Smack sample." into klp-modular-docs
This commit is contained in:
@@ -535,6 +535,8 @@ import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
|||||||
import org.jivesoftware.smack.ConnectionListener;
|
import org.jivesoftware.smack.ConnectionListener;
|
||||||
import org.jivesoftware.smack.PacketInterceptor;
|
import org.jivesoftware.smack.PacketInterceptor;
|
||||||
import org.jivesoftware.smack.PacketListener;
|
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.XMPPConnection;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.filter.PacketTypeFilter;
|
import org.jivesoftware.smack.filter.PacketTypeFilter;
|
||||||
@@ -544,352 +546,378 @@ import org.jivesoftware.smack.packet.Packet;
|
|||||||
import org.jivesoftware.smack.packet.PacketExtension;
|
import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
import org.jivesoftware.smack.provider.PacketExtensionProvider;
|
import org.jivesoftware.smack.provider.PacketExtensionProvider;
|
||||||
import org.jivesoftware.smack.provider.ProviderManager;
|
import org.jivesoftware.smack.provider.ProviderManager;
|
||||||
|
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
import org.json.simple.JSONValue;
|
import org.json.simple.JSONValue;
|
||||||
import org.json.simple.parser.ParseException;
|
import org.json.simple.parser.ParseException;
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.UUID;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
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.
|
* <p>For illustration purposes only.
|
||||||
*/
|
*/
|
||||||
public class SmackCcsClient {
|
public class SmackCcsClient {
|
||||||
|
|
||||||
Logger logger = Logger.getLogger("SmackCcsClient");
|
private static final Logger logger = Logger.getLogger("SmackCcsClient");
|
||||||
|
|
||||||
public static final String GCM_SERVER = "gcm.googleapis.com";
|
private static final String GCM_SERVER = "gcm.googleapis.com";
|
||||||
public static final int GCM_PORT = 5235;
|
private static final int GCM_PORT = 5235;
|
||||||
|
|
||||||
public static final String GCM_ELEMENT_NAME = "gcm";
|
private static final String GCM_ELEMENT_NAME = "gcm";
|
||||||
public static final String GCM_NAMESPACE = "google:mobile:data";
|
private static final String GCM_NAMESPACE = "google:mobile:data";
|
||||||
|
|
||||||
static Random random = new Random();
|
static {
|
||||||
XMPPConnection connection;
|
|
||||||
ConnectionConfiguration config;
|
|
||||||
|
|
||||||
/**
|
ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE,
|
||||||
* XMPP Packet Extension for GCM Cloud Connection Server.
|
new PacketExtensionProvider() {
|
||||||
*/
|
@Override
|
||||||
class GcmPacketExtension extends DefaultPacketExtension {
|
public PacketExtension parseExtension(XmlPullParser parser) throws
|
||||||
String json;
|
Exception {
|
||||||
|
String json = parser.nextText();
|
||||||
public GcmPacketExtension(String json) {
|
return new GcmPacketExtension(json);
|
||||||
super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
|
}
|
||||||
this.json = json;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getJson() {
|
private XMPPConnection connection;
|
||||||
return json;
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public String toXML() {
|
* Returns a random message id to uniquely identify a message.
|
||||||
return String.format("<%s xmlns=\"%s\">%s</%s>", GCM_ELEMENT_NAME,
|
*
|
||||||
GCM_NAMESPACE, json, GCM_ELEMENT_NAME);
|
* <p>Note: This is generated by a pseudo random number generator for
|
||||||
|
* illustration purpose, and is not guaranteed to be unique.
|
||||||
|
*/
|
||||||
|
public String nextMessageId() {
|
||||||
|
return "m-" + UUID.randomUUID().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
/**
|
||||||
public Packet toPacket() {
|
* Sends a packet with contents provided.
|
||||||
return new Message() {
|
*/
|
||||||
// Must override toXML() because it includes a <body>
|
protected void send(String jsonRequest) throws NotConnectedException {
|
||||||
|
Packet request = new GcmPacketExtension(jsonRequest).toPacket();
|
||||||
|
connection.sendPacket(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 properly process upstream messages.
|
||||||
|
*/
|
||||||
|
protected void handleUpstreamMessage(Map<String, Object> jsonObject) {
|
||||||
|
// PackageName of the application that sent this message.
|
||||||
|
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, 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>Logs a {@code INFO} message, but subclasses could override it to
|
||||||
|
* properly handle ACKs.
|
||||||
|
*/
|
||||||
|
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>Logs a {@code INFO} message, but subclasses could override it to
|
||||||
|
* properly handle NACKs.
|
||||||
|
*/
|
||||||
|
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 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) {
|
||||||
|
Map<String, Object> message = new HashMap<String, Object>();
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
message.put("message_id", messageId);
|
||||||
|
return JSONValue.toJSONString(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to GCM Cloud Connection Server using the supplied credentials.
|
||||||
|
*
|
||||||
|
* @param senderId Your GCM project number
|
||||||
|
* @param apiKey API Key of your project
|
||||||
|
*/
|
||||||
|
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());
|
||||||
|
|
||||||
|
connection = new XMPPTCPConnection(config);
|
||||||
|
connection.connect();
|
||||||
|
|
||||||
|
connection.addConnectionListener(new LoggingConnectionListener());
|
||||||
|
|
||||||
|
// 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<String, Object> jsonObject =
|
||||||
|
(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
|
||||||
|
handleUpstreamMessage(jsonObject);
|
||||||
|
|
||||||
|
// Send ACK to CCS
|
||||||
|
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())) {
|
||||||
|
// Process Ack
|
||||||
|
handleAckReceipt(jsonObject);
|
||||||
|
} 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)",
|
||||||
|
messageType.toString());
|
||||||
|
}
|
||||||
|
} catch (ParseException e) {
|
||||||
|
logger.log(Level.SEVERE, "Error parsing JSON " + json, e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.log(Level.SEVERE, "Failed to process packet", 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(senderId + "@gcm.googleapis.com", apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
ccsClient.connect(senderId, password);
|
||||||
|
|
||||||
|
// Send a sample hello downstream message to a device.
|
||||||
|
String toRegId = "RegistrationIdOfTheTargetDevice";
|
||||||
|
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;
|
||||||
|
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
|
@Override
|
||||||
public String toXML() {
|
public String toXML() {
|
||||||
|
return String.format("<%s xmlns=\"%s\">%s</%s>",
|
||||||
StringBuilder buf = new StringBuilder();
|
GCM_ELEMENT_NAME, GCM_NAMESPACE,
|
||||||
buf.append("<message");
|
StringUtils.escapeForXML(json), GCM_ELEMENT_NAME);
|
||||||
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 SmackCcsClient() {
|
public Packet toPacket() {
|
||||||
// Add GcmPacketExtension
|
Message message = new Message();
|
||||||
ProviderManager.getInstance().addExtensionProvider(GCM_ELEMENT_NAME,
|
message.addExtension(this);
|
||||||
GCM_NAMESPACE, new PacketExtensionProvider() {
|
return message;
|
||||||
|
|
||||||
@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.
|
|
||||||
*
|
|
||||||
* <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());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* <p>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<String, Object> 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<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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles an ACK.
|
|
||||||
*
|
|
||||||
* <p>By default, it only 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles a NACK.
|
|
||||||
*
|
|
||||||
* <p>By default, it only 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<String, String> payload,
|
|
||||||
String collapseKey, Long timeToLive, Boolean delayWhileIdle) {
|
|
||||||
Map<String, Object> message = new HashMap<String, Object>();
|
|
||||||
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<String, Object> message = new HashMap<String, Object>();
|
|
||||||
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<String, Object> jsonObject =
|
|
||||||
(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);
|
|
||||||
|
|
||||||
// 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 main(String [] args) {
|
|
||||||
final String userName = "Your GCM Sender Id" + "@gcm.googleapis.com";
|
|
||||||
final String password = "API Key";
|
|
||||||
|
|
||||||
SmackCcsClient ccsClient = new SmackCcsClient();
|
|
||||||
|
|
||||||
try {
|
|
||||||
ccsClient.connect(userName, password);
|
|
||||||
} catch (XMPPException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a sample hello downstream message to a device.
|
private static final class LoggingConnectionListener
|
||||||
String toRegId = "RegistrationIdOfTheTargetDevice";
|
implements ConnectionListener {
|
||||||
String messageId = ccsClient.getRandomMessageId();
|
|
||||||
Map<String, String> payload = new HashMap<String, String>();
|
@Override
|
||||||
payload.put("Hello", "World");
|
public void connected(XMPPConnection xmppConnection) {
|
||||||
payload.put("CCS", "Dummy Message");
|
logger.info("Connected.");
|
||||||
payload.put("EmbeddedMessageId", messageId);
|
}
|
||||||
String collapseKey = "sample";
|
|
||||||
Long timeToLive = 10000L;
|
@Override
|
||||||
Boolean delayWhileIdle = true;
|
public void authenticated(XMPPConnection xmppConnection) {
|
||||||
ccsClient.send(createJsonMessage(toRegId, messageId, payload, collapseKey,
|
logger.info("Authenticated.");
|
||||||
timeToLive, delayWhileIdle));
|
}
|
||||||
}
|
|
||||||
|
@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>
|
}</pre>
|
||||||
|
|
||||||
<h3 id="python">Python sample</h3>
|
<h3 id="python">Python sample</h3>
|
||||||
|
|
||||||
<p>Here is an example of a CCS app server written in Python. This sample echo
|
<p>Here is an example of a CCS app server written in Python. This sample echo
|
||||||
|
|||||||
Reference in New Issue
Block a user