Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fc8a3b5f authored by kmccormick's avatar kmccormick Committed by Android (Google) Code Review
Browse files

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

parents 046702ba 9c311fb4
Loading
Loading
Loading
Loading
+320 −292
Original line number Diff line number Diff line
@@ -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;
@@ -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);
    }
@@ -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) {
@@ -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);
@@ -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() {
@@ -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())) {
@@ -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
@@ -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