getAccounts() {
90 | return Collections.unmodifiableSet(mUserMap.keySet());
91 | }
92 |
93 | public String getUniqueMessageId() {
94 | int nextRandom = sRandom.nextInt();
95 | while (mMessageIds.contains(nextRandom)) {
96 | nextRandom = sRandom.nextInt();
97 | }
98 | return Integer.toString(nextRandom);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Readme
2 | This is a sample project to showcase Google Cloud Messaging using upstream messaging. This
3 | project is a very simply standalone server. Though that's not part of this sample
4 | I was able to utilize this project from within a Java webapp.
5 |
6 | The project was originally created for a talk I held at the [Dutch Android User Group](www.dutchaug.org)
7 | meeting in Utrecht on January, 16th 2014. If you ever have the possibility to
8 | visit a meeting of the Dutch AUG, I stringly recommend to do so. It has been a gorgeous evening!
9 |
10 |
11 | ## Libraries used
12 | This project makes use of [Smack](http://www.igniterealtime.org/projects/smack/) and
13 | [Json-simple](http://code.google.com/p/json-simple/). The code is based on a sample class provided
14 | by Google on the [XMPP documentation page](http://developer.android.com/google/gcm/ccs.html).
15 |
16 |
17 | ## Most important classes
18 | All classes are within the `com.grokkingandroid.sampleapp.samples.gcm.ccs.server` package.
19 |
20 | `CcsClient` contains a main method which takes three arguments:
21 |
22 | 1. The project number
23 | 2. The API key
24 | 3. A registration id to send a test message to
25 |
26 | If you start it that way, the GUI of the Smack library is used to show incoming and outgoing messages.
27 |
28 | Of course, you also can use `CcsClient` from within any other project. In my case I used it from within
29 | a web project to send messages to the Android client.
30 |
31 | In that case you first have to call `prepareClient()` and pass it the project number and the api key as arguments.
32 | The third argument decides whether the GUI should be shown or not. On servers you have to set this to `false`.
33 |
34 | The registration ids of the Android clients and all message are stored in memory. This is managed by the
35 | `PseudoDao` class. This should help you to get started with a real persistence solution for production.
36 |
37 | For this sample all incoming messages must follow a certain format. That is, they must contain at least
38 | an `action` key with a supported value. This `action` key determines which `PayloadProcessor` to create.
39 | The implementations of `PayloadProcessor` (`EchoProcessor`, `MessageProcessor` and `RegisterProcessor`)
40 | finally handle the incoming messages and perform the appropriate actions.
41 |
42 |
43 | ## Credentials
44 | **To run this project you need a GCM-project number and an API key.** You can read more about it on the
45 | [Getting Started page of Google's documentation](http://developer.android.com/google/gcm/gs.html).
46 |
47 | Those are passed into the `CcsClient` by either calling the `prepareClient()` method or by providing those
48 | as arguments to the main method.
49 |
50 | Keep in mind: For using Google Cloud Messaging with XMPP **you need to apply first**.
51 | See for example the note at the top of the [http://developer.android.com/google/gcm/notifications.html](User Notification page).
52 | You can find a current link there as well.
53 |
54 |
55 | ## Relevant Blogposts on [Grokking Android](http://www.grokkingandroid.com/)
56 | Right now I haven't finished the blog post about GCM. It's in the making and should be up pretty soon.
57 |
58 |
59 | ## Developed by
60 |
61 | *Wolfram Rittmeyer* - You can contact me via:
62 |
63 | * [Grokking Android (my blog)](http://www.grokkingandroid.com)
64 |
65 | * [Google+](https://plus.google.com/+WolframRittmeyer)
66 |
67 | * [Twitter](https://twitter.com/RittmeyerW)
68 |
69 |
70 | ## Thanks
71 | A big thanks to the organizers of the meetup in Utrecht. It was a great evening with good presentations
72 | and interesting conversations in the breaks!
73 |
74 | Also thanks to all my readers and blog commenters. Your feedback helps me to stay on track and to go on
75 | with projects like this. Without my blog (and thus you) I probably woouln't have been invited in the first place :-)
76 |
77 | Special thanks also to the Android crowd on G+. You're an awesome crowd. I have gained many insights
78 | by reading posts there, by following links to blog posts or by discussions on G+! And it was great to meet some of you
79 | in Utrecht.
80 |
81 | And finally: This readme was created using [dillinger.io](http://dillinger.io). Thanks for this service.
82 |
83 |
84 | ## License
85 | Copyright 2014 Wolfram Rittmeyer
86 |
87 | Licensed under the Apache License, Version 2.0 (the "License");
88 | you may not use this file except in compliance with the License.
89 | You may obtain a copy of the License at
90 |
91 | http://www.apache.org/licenses/LICENSE-2.0
92 |
93 | Unless required by applicable law or agreed to in writing, software
94 | distributed under the License is distributed on an "AS IS" BASIS,
95 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
96 | See the License for the specific language governing permissions and
97 | limitations under the License.
98 |
99 |
--------------------------------------------------------------------------------
/LICENSE-2.0.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/src/com/grokkingandroid/sampleapp/samples/gcm/ccs/server/CcsClient.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Wolfram Rittmeyer.
3 | *
4 | * Portions Copyright Google Inc.
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.grokkingandroid.sampleapp.samples.gcm.ccs.server;
19 |
20 | import org.jivesoftware.smack.ConnectionConfiguration;
21 | import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
22 | import org.jivesoftware.smack.ConnectionListener;
23 | import org.jivesoftware.smack.PacketInterceptor;
24 | import org.jivesoftware.smack.PacketListener;
25 | import org.jivesoftware.smack.XMPPConnection;
26 | import org.jivesoftware.smack.XMPPException;
27 | import org.jivesoftware.smack.filter.PacketTypeFilter;
28 | import org.jivesoftware.smack.packet.DefaultPacketExtension;
29 | import org.jivesoftware.smack.packet.Message;
30 | import org.jivesoftware.smack.packet.Packet;
31 | import org.jivesoftware.smack.packet.PacketExtension;
32 | import org.jivesoftware.smack.provider.PacketExtensionProvider;
33 | import org.jivesoftware.smack.provider.ProviderManager;
34 | import org.jivesoftware.smack.util.StringUtils;
35 | import org.json.simple.JSONValue;
36 | import org.json.simple.parser.ParseException;
37 | import org.xmlpull.v1.XmlPullParser;
38 |
39 | import java.util.HashMap;
40 | import java.util.List;
41 | import java.util.Map;
42 | import java.util.Random;
43 | import java.util.logging.Level;
44 | import java.util.logging.Logger;
45 |
46 | import javax.net.ssl.SSLSocketFactory;
47 |
48 | /**
49 | * Sample Smack implementation of a client for GCM Cloud Connection Server.
50 | * Most of it has been taken more or less verbatim from Googles
51 | * documentation: http://developer.android.com/google/gcm/ccs.html
52 | *
53 | * But some additions have been made. Bigger changes are annotated like that:
54 | * "/// new".
55 | *
56 | * Those changes have to do with parsing certain type of messages
57 | * as well as with sending messages to a list of recipients. The original code
58 | * only covers sending one message to exactly one recipient.
59 | */
60 | public class CcsClient {
61 |
62 | public static final Logger logger = Logger.getLogger(CcsClient.class.getName());
63 |
64 | public static final String GCM_SERVER = "gcm.googleapis.com";
65 | public static final int GCM_PORT = 5235;
66 |
67 | public static final String GCM_ELEMENT_NAME = "gcm";
68 | public static final String GCM_NAMESPACE = "google:mobile:data";
69 |
70 | static Random random = new Random();
71 | XMPPConnection connection;
72 | ConnectionConfiguration config;
73 |
74 | /// new: some additional instance and class members
75 | private static CcsClient sInstance = null;
76 | private String mApiKey = null;
77 | private String mProjectId = null;
78 | private boolean mDebuggable = false;
79 |
80 | /**
81 | * XMPP Packet Extension for GCM Cloud Connection Server.
82 | */
83 | class GcmPacketExtension extends DefaultPacketExtension {
84 |
85 | String json;
86 |
87 | public GcmPacketExtension(String json) {
88 | super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
89 | this.json = json;
90 | }
91 |
92 | public String getJson() {
93 | return json;
94 | }
95 |
96 | @Override
97 | public String toXML() {
98 | return String.format("<%s xmlns=\"%s\">%s%s>", GCM_ELEMENT_NAME,
99 | GCM_NAMESPACE, json, GCM_ELEMENT_NAME);
100 | }
101 |
102 | @SuppressWarnings("unused")
103 | public Packet toPacket() {
104 | return new Message() {
105 | // Must override toXML() because it includes a
106 | @Override
107 | public String toXML() {
108 |
109 | StringBuilder buf = new StringBuilder();
110 | buf.append("");
127 | buf.append(GcmPacketExtension.this.toXML());
128 | buf.append("");
129 | return buf.toString();
130 | }
131 | };
132 | }
133 | }
134 |
135 | public static CcsClient getInstance() {
136 | if (sInstance == null) {
137 | throw new IllegalStateException("You have to prepare the client first");
138 | }
139 | return sInstance;
140 | }
141 |
142 | public static CcsClient prepareClient(String projectId, String apiKey, boolean debuggable) {
143 | synchronized(CcsClient.class) {
144 | if (sInstance == null) {
145 | sInstance = new CcsClient(projectId, apiKey, debuggable);
146 | }
147 | }
148 | return sInstance;
149 | }
150 |
151 | private CcsClient(String projectId, String apiKey, boolean debuggable) {
152 | this();
153 | mApiKey = apiKey;
154 | mProjectId = projectId;
155 | mDebuggable = debuggable;
156 | }
157 |
158 | private CcsClient() {
159 | // Add GcmPacketExtension
160 | ProviderManager.getInstance().addExtensionProvider(GCM_ELEMENT_NAME,
161 | GCM_NAMESPACE, new PacketExtensionProvider() {
162 |
163 | @Override
164 | public PacketExtension parseExtension(XmlPullParser parser)
165 | throws Exception {
166 | String json = parser.nextText();
167 | GcmPacketExtension packet = new GcmPacketExtension(json);
168 | return packet;
169 | }
170 | });
171 | }
172 |
173 | /**
174 | * Returns a random message id to uniquely identify a message.
175 | *
176 | *
177 | * Note: This is generated by a pseudo random number generator for
178 | * illustration purpose, and is not guaranteed to be unique.
179 | *
180 | */
181 | public String getRandomMessageId() {
182 | return "m-" + Long.toString(random.nextLong());
183 | }
184 |
185 | /**
186 | * Sends a downstream GCM message.
187 | */
188 | public void send(String jsonRequest) {
189 | Packet request = new GcmPacketExtension(jsonRequest).toPacket();
190 | connection.sendPacket(request);
191 | }
192 |
193 | /// new: for sending messages to a list of recipients
194 | /**
195 | * Sends a message to multiple recipients. Kind of like the old
196 | * HTTP message with the list of regIds in the "registration_ids" field.
197 | */
198 | public void sendBroadcast(Map payload, String collapseKey,
199 | long timeToLive, Boolean delayWhileIdle, List recipients) {
200 | Map map = createAttributeMap(null, null, payload, collapseKey,
201 | timeToLive, delayWhileIdle);
202 | for (String toRegId: recipients) {
203 | String messageId = getRandomMessageId();
204 | map.put("message_id", messageId);
205 | map.put("to", toRegId);
206 | String jsonRequest = createJsonMessage(map);
207 | send(jsonRequest);
208 | }
209 | }
210 |
211 | /// new: customized version of the standard handleIncomingDateMessage method
212 | /**
213 | * Handles an upstream data message from a device application.
214 | */
215 | public void handleIncomingDataMessage(CcsMessage msg) {
216 | if (msg.getPayload().get("action") != null) {
217 | PayloadProcessor processor = ProcessorFactory.getProcessor(msg.getPayload().get("action"));
218 | processor.handleMessage(msg);
219 | }
220 | }
221 |
222 | /// new: was previously part of the previous method
223 | /**
224 | *
225 | */
226 | private CcsMessage getMessage(Map jsonObject) {
227 | String from = jsonObject.get("from").toString();
228 |
229 | // PackageName of the application that sent this message.
230 | String category = jsonObject.get("category").toString();
231 |
232 | // unique id of this message
233 | String messageId = jsonObject.get("message_id").toString();
234 |
235 | @SuppressWarnings("unchecked")
236 | Map payload = (Map) jsonObject.get("data");
237 |
238 | CcsMessage msg = new CcsMessage(from, category, messageId, payload);
239 |
240 | return msg;
241 | }
242 |
243 | /**
244 | * Handles an ACK.
245 | *
246 | *
247 | * By default, it only logs a INFO message, but subclasses could override it
248 | * to properly handle ACKS.
249 | */
250 | public void handleAckReceipt(Map jsonObject) {
251 | String messageId = jsonObject.get("message_id").toString();
252 | String from = jsonObject.get("from").toString();
253 | logger.log(Level.INFO, "handleAckReceipt() from: " + from + ", messageId: " + messageId);
254 | }
255 |
256 | /**
257 | * Handles a NACK.
258 | *
259 | *
260 | * By default, it only logs a INFO message, but subclasses could override it
261 | * to properly handle NACKS.
262 | */
263 | public void handleNackReceipt(Map jsonObject) {
264 | String messageId = jsonObject.get("message_id").toString();
265 | String from = jsonObject.get("from").toString();
266 | logger.log(Level.INFO, "handleNackReceipt() from: " + from + ", messageId: " + messageId);
267 | }
268 |
269 | /**
270 | * Creates a JSON encoded GCM message.
271 | *
272 | * @param to RegistrationId of the target device (Required).
273 | * @param messageId Unique messageId for which CCS will send an "ack/nack"
274 | * (Required).
275 | * @param payload Message content intended for the application. (Optional).
276 | * @param collapseKey GCM collapse_key parameter (Optional).
277 | * @param timeToLive GCM time_to_live parameter (Optional).
278 | * @param delayWhileIdle GCM delay_while_idle parameter (Optional).
279 | * @return JSON encoded GCM message.
280 | */
281 | public static String createJsonMessage(String to, String messageId, Map payload,
282 | String collapseKey, Long timeToLive, Boolean delayWhileIdle) {
283 | return createJsonMessage(createAttributeMap(to, messageId, payload,
284 | collapseKey, timeToLive, delayWhileIdle));
285 | }
286 |
287 | public static String createJsonMessage(Map map) {
288 | return JSONValue.toJSONString(map);
289 | }
290 |
291 | public static Map createAttributeMap(String to, String messageId, Map payload,
292 | String collapseKey, Long timeToLive, Boolean delayWhileIdle) {
293 | Map message = new HashMap();
294 | if (to != null) {
295 | message.put("to", to);
296 | }
297 | if (collapseKey != null) {
298 | message.put("collapse_key", collapseKey);
299 | }
300 | if (timeToLive != null) {
301 | message.put("time_to_live", timeToLive);
302 | }
303 | if (delayWhileIdle != null && delayWhileIdle) {
304 | message.put("delay_while_idle", true);
305 | }
306 | if (messageId != null) {
307 | message.put("message_id", messageId);
308 | }
309 | message.put("data", payload);
310 | return message;
311 | }
312 |
313 | /**
314 | * Creates a JSON encoded ACK message for an upstream message received from
315 | * an application.
316 | *
317 | * @param to RegistrationId of the device who sent the upstream message.
318 | * @param messageId messageId of the upstream message to be acknowledged to
319 | * CCS.
320 | * @return JSON encoded ack.
321 | */
322 | public static String createJsonAck(String to, String messageId) {
323 | Map message = new HashMap();
324 | message.put("message_type", "ack");
325 | message.put("to", to);
326 | message.put("message_id", messageId);
327 | return JSONValue.toJSONString(message);
328 | }
329 |
330 | /// new: NACK added
331 | /**
332 | * Creates a JSON encoded NACK message for an upstream message received from
333 | * an application.
334 | *
335 | * @param to RegistrationId of the device who sent the upstream message.
336 | * @param messageId messageId of the upstream message to be acknowledged to
337 | * CCS.
338 | * @return JSON encoded nack.
339 | */
340 | public static String createJsonNack(String to, String messageId) {
341 | Map message = new HashMap();
342 | message.put("message_type", "nack");
343 | message.put("to", to);
344 | message.put("message_id", messageId);
345 | return JSONValue.toJSONString(message);
346 | }
347 |
348 | /**
349 | * Connects to GCM Cloud Connection Server using the supplied credentials.
350 | * @throws XMPPException
351 | */
352 | public void connect() throws XMPPException {
353 | config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
354 | config.setSecurityMode(SecurityMode.enabled);
355 | config.setReconnectionAllowed(true);
356 | config.setRosterLoadedAtLogin(false);
357 | config.setSendPresence(false);
358 | config.setSocketFactory(SSLSocketFactory.getDefault());
359 |
360 | // NOTE: Set to true to launch a window with information about packets sent and received
361 | config.setDebuggerEnabled(mDebuggable);
362 |
363 | // -Dsmack.debugEnabled=true
364 | XMPPConnection.DEBUG_ENABLED = true;
365 |
366 | connection = new XMPPConnection(config);
367 | connection.connect();
368 |
369 | connection.addConnectionListener(new ConnectionListener() {
370 |
371 | @Override
372 | public void reconnectionSuccessful() {
373 | logger.info("Reconnecting..");
374 | }
375 |
376 | @Override
377 | public void reconnectionFailed(Exception e) {
378 | logger.log(Level.INFO, "Reconnection failed.. ", e);
379 | }
380 |
381 | @Override
382 | public void reconnectingIn(int seconds) {
383 | logger.log(Level.INFO, "Reconnecting in %d secs", seconds);
384 | }
385 |
386 | @Override
387 | public void connectionClosedOnError(Exception e) {
388 | logger.log(Level.INFO, "Connection closed on error.");
389 | }
390 |
391 | @Override
392 | public void connectionClosed() {
393 | logger.info("Connection closed.");
394 | }
395 | });
396 |
397 | // Handle incoming packets
398 | connection.addPacketListener(new PacketListener() {
399 |
400 | @Override
401 | public void processPacket(Packet packet) {
402 | logger.log(Level.INFO, "Received: " + packet.toXML());
403 | Message incomingMessage = (Message) packet;
404 | GcmPacketExtension gcmPacket
405 | = (GcmPacketExtension) incomingMessage.getExtension(GCM_NAMESPACE);
406 | String json = gcmPacket.getJson();
407 | try {
408 | @SuppressWarnings("unchecked")
409 | Map jsonMap
410 | = (Map) JSONValue.parseWithException(json);
411 |
412 | handleMessage(jsonMap);
413 | } catch (ParseException e) {
414 | logger.log(Level.SEVERE, "Error parsing JSON " + json, e);
415 | } catch (Exception e) {
416 | logger.log(Level.SEVERE, "Couldn't send echo.", e);
417 | }
418 | }
419 | }, new PacketTypeFilter(Message.class));
420 |
421 | // Log all outgoing packets
422 | connection.addPacketInterceptor(new PacketInterceptor() {
423 | @Override
424 | public void interceptPacket(Packet packet) {
425 | logger.log(Level.INFO, "Sent: {0}", packet.toXML());
426 | }
427 | }, new PacketTypeFilter(Message.class));
428 |
429 | connection.login(mProjectId + "@gcm.googleapis.com", mApiKey);
430 | logger.log(Level.INFO, "logged in: " + mProjectId);
431 | }
432 |
433 | private void handleMessage(Map jsonMap) {
434 | // present for "ack"/"nack", null otherwise
435 | Object messageType = jsonMap.get("message_type");
436 |
437 | if (messageType == null) {
438 | CcsMessage msg = getMessage(jsonMap);
439 | // Normal upstream data message
440 | try {
441 | handleIncomingDataMessage(msg);
442 | // Send ACK to CCS
443 | String ack = createJsonAck(msg.getFrom(), msg.getMessageId());
444 | send(ack);
445 | }
446 | catch (Exception e) {
447 | // Send NACK to CCS
448 | String nack = createJsonNack(msg.getFrom(), msg.getMessageId());
449 | send(nack);
450 | }
451 | } else if ("ack".equals(messageType.toString())) {
452 | // Process Ack
453 | handleAckReceipt(jsonMap);
454 | } else if ("nack".equals(messageType.toString())) {
455 | // Process Nack
456 | handleNackReceipt(jsonMap);
457 | } else {
458 | logger.log(Level.WARNING, "Unrecognized message type (%s)",
459 | messageType.toString());
460 | }
461 | }
462 |
463 | public static void main(String[] args) {
464 | final String projectId = args[0];
465 | final String password = args[1];
466 | final String toRegId = args[2];
467 |
468 | CcsClient ccsClient = CcsClient.prepareClient(projectId, password, true);
469 |
470 | try {
471 | ccsClient.connect();
472 | } catch (XMPPException e) {
473 | e.printStackTrace();
474 | }
475 |
476 | // Send a sample hello downstream message to a device.
477 | String messageId = ccsClient.getRandomMessageId();
478 | Map payload = new HashMap();
479 | payload.put("message", "Simple sample sessage");
480 | String collapseKey = "sample";
481 | Long timeToLive = 10000L;
482 | Boolean delayWhileIdle = true;
483 | ccsClient.send(createJsonMessage(toRegId, messageId, payload, collapseKey,
484 | timeToLive, delayWhileIdle));
485 | }
486 | }
487 |
--------------------------------------------------------------------------------