Merge "Doc Update: new Smack sample." into klp-modular-docs

This commit is contained in:
kmccormick
2014-07-16 21:02:36 +00:00
committed by Android (Google) Code Review

View File

@@ -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,122 +546,87 @@ 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;
/**
* 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;
}
ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE,
new PacketExtensionProvider() {
@Override @Override
public String toXML() { public PacketExtension parseExtension(XmlPullParser parser) throws
return String.format("<%s xmlns=\"%s\">%s</%s>", GCM_ELEMENT_NAME, Exception {
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 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(); String json = parser.nextText();
GcmPacketExtension packet = new GcmPacketExtension(json); return new GcmPacketExtension(json);
return packet;
} }
}); });
} }
private XMPPConnection connection;
/**
* 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. * Returns a random message id to uniquely identify a message.
* *
* <p>Note: * <p>Note: This is generated by a pseudo random number generator for
* This is generated by a pseudo random number generator for illustration purpose, * illustration purpose, and is not guaranteed to be unique.
* and is not guaranteed to be unique.
*
*/ */
public String getRandomMessageId() { public String nextMessageId() {
return "m-" + Long.toString(random.nextLong()); 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(); Packet request = new GcmPacketExtension(jsonRequest).toPacket();
connection.sendPacket(request); connection.sendPacket(request);
} }
@@ -668,62 +635,81 @@ public class SmackCcsClient {
* Handles an upstream data message from a device application. * Handles an upstream data message from a device application.
* *
* <p>This sample echo server sends an echo message back to the device. * <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) { protected void handleUpstreamMessage(Map<String, Object> jsonObject) {
String from = jsonObject.get("from").toString();
// PackageName of the application that sent this message. // PackageName of the application that sent this message.
String category = jsonObject.get("category").toString(); String category = (String) jsonObject.get("category");
String from = (String) jsonObject.get("from");
// Use the packageName as the collapseKey in the echo packet
String collapseKey = "echo:CollapseKey";
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, String> payload = (Map<String, String>) jsonObject.get("data"); Map<String, String> payload = (Map<String, String>) jsonObject.get("data");
payload.put("ECHO", "Application: " + category); payload.put("ECHO", "Application: " + category);
// Send an ECHO response back // Send an ECHO response back
String echo = createJsonMessage(from, getRandomMessageId(), payload, collapseKey, null, false); String echo = createJsonMessage(from, nextMessageId(), payload,
send(echo); "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. * Handles an ACK.
* *
* <p>By default, it only logs a {@code INFO} message, but subclasses could override it to * <p>Logs a {@code INFO} message, but subclasses could override it to
* properly handle ACKS. * properly handle ACKs.
*/ */
public void handleAckReceipt(Map<String, Object> jsonObject) { protected void handleAckReceipt(Map<String, Object> jsonObject) {
String messageId = jsonObject.get("message_id").toString(); String messageId = (String) jsonObject.get("message_id");
String from = jsonObject.get("from").toString(); String from = (String) jsonObject.get("from");
logger.log(Level.INFO, "handleAckReceipt() from: " + from + ", messageId: " + messageId); logger.log(Level.INFO, "handleAckReceipt() from: " + from + ",
messageId: " + messageId);
} }
/** /**
* Handles a NACK. * Handles a NACK.
* *
* <p>By default, it only logs a {@code INFO} message, but subclasses could override it to * <p>Logs a {@code INFO} message, but subclasses could override it to
* properly handle NACKS. * properly handle NACKs.
*/ */
public void handleNackReceipt(Map<String, Object> jsonObject) { protected void handleNackReceipt(Map<String, Object> jsonObject) {
String messageId = jsonObject.get("message_id").toString(); String messageId = (String) jsonObject.get("message_id");
String from = jsonObject.get("from").toString(); String from = (String) jsonObject.get("from");
logger.log(Level.INFO, "handleNackReceipt() from: " + from + ", messageId: " + messageId); 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. * Creates a JSON encoded GCM message.
* *
* @param to RegistrationId of the target device (Required). * @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 payload Message content intended for the application. (Optional).
* @param collapseKey GCM collapse_key parameter (Optional). * @param collapseKey GCM collapse_key parameter (Optional).
* @param timeToLive GCM time_to_live parameter (Optional). * @param timeToLive GCM time_to_live parameter (Optional).
* @param delayWhileIdle GCM delay_while_idle parameter (Optional). * @param delayWhileIdle GCM delay_while_idle parameter (Optional).
* @return JSON encoded GCM message. * @return JSON encoded GCM message.
*/ */
public static String createJsonMessage(String to, String messageId, Map<String, String> payload, public static String createJsonMessage(String to, String messageId,
String collapseKey, Long timeToLive, Boolean delayWhileIdle) { Map<String, String> payload, String collapseKey, Long timeToLive,
Boolean delayWhileIdle) {
Map<String, Object> message = new HashMap<String, Object>(); Map<String, Object> message = new HashMap<String, Object>();
message.put("to", to); message.put("to", to);
if (collapseKey != null) { if (collapseKey != null) {
@@ -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 to RegistrationId of the device who sent the upstream message.
* @param messageId messageId of the upstream message to be acknowledged to CCS. * @param messageId messageId of the upstream message to be acknowledged to CCS.
* @return JSON encoded ack. * @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>(); Map<String, Object> message = new HashMap<String, Object>();
message.put("message_type", "ack"); message.put("message_type", "ack");
message.put("to", to); message.put("to", to);
@@ -758,28 +745,151 @@ public class SmackCcsClient {
/** /**
* Connects to GCM Cloud Connection Server using the supplied credentials. * Connects to GCM Cloud Connection Server using the supplied credentials.
* *
* @param username GCM_SENDER_ID@gcm.googleapis.com * @param senderId Your GCM project number
* @param password API Key * @param apiKey API Key of your project
* @throws XMPPException
*/ */
public void connect(String username, String password) throws XMPPException { public void connect(long senderId, String apiKey)
config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT); throws XMPPException, IOException, SmackException {
ConnectionConfiguration config =
new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
config.setSecurityMode(SecurityMode.enabled); config.setSecurityMode(SecurityMode.enabled);
config.setReconnectionAllowed(true); config.setReconnectionAllowed(true);
config.setRosterLoadedAtLogin(false); config.setRosterLoadedAtLogin(false);
config.setSendPresence(false); config.setSendPresence(false);
config.setSocketFactory(SSLSocketFactory.getDefault()); config.setSocketFactory(SSLSocketFactory.getDefault());
// NOTE: Set to true to launch a window with information about packets sent and received connection = new XMPPTCPConnection(config);
config.setDebuggerEnabled(true);
// -Dsmack.debugEnabled=true
XMPPConnection.DEBUG_ENABLED = true;
connection = new XMPPConnection(config);
connection.connect(); connection.connect();
connection.addConnectionListener(new ConnectionListener() { 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
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 @Override
public void reconnectionSuccessful() { public void reconnectionSuccessful() {
@@ -798,98 +908,16 @@ public class SmackCcsClient {
@Override @Override
public void connectionClosedOnError(Exception e) { public void connectionClosedOnError(Exception e) {
logger.log(Level.INFO, "Connection closed on error."); logger.info("Connection closed on error.");
} }
@Override @Override
public void connectionClosed() { public void connectionClosed() {
logger.info("Connection closed."); 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.
String toRegId = "RegistrationIdOfTheTargetDevice";
String messageId = ccsClient.getRandomMessageId();
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));
} }
}</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