diff --git a/Push/GCMCommand/.classpath b/Push/GCMCommand/.classpath
new file mode 100644
index 000000000..bbbad2cd3
--- /dev/null
+++ b/Push/GCMCommand/.classpath
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/Push/GCMCommand/.project b/Push/GCMCommand/.project
new file mode 100644
index 000000000..920aa8961
--- /dev/null
+++ b/Push/GCMCommand/.project
@@ -0,0 +1,17 @@
+
+
+ GCMCommand
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/Push/GCMCommand/dist/gcm-cmd.jar b/Push/GCMCommand/dist/gcm-cmd.jar
new file mode 100644
index 000000000..e73447a56
Binary files /dev/null and b/Push/GCMCommand/dist/gcm-cmd.jar differ
diff --git a/Push/GCMCommand/gcm b/Push/GCMCommand/gcm
new file mode 100755
index 000000000..fe33eac55
--- /dev/null
+++ b/Push/GCMCommand/gcm
@@ -0,0 +1,2 @@
+#!/bin/bash
+java -cp libs/commons-cli-1.2.jar:libs/gcm-server.jar:libs/json_simple-1.1.jar:dist/gcm-cmd.jar com.commonsware.android.gcm.cmd.GCM "$@"
diff --git a/Push/GCMCommand/gcm.jardesc b/Push/GCMCommand/gcm.jardesc
new file mode 100644
index 000000000..8b4b3eb44
--- /dev/null
+++ b/Push/GCMCommand/gcm.jardesc
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Push/GCMCommand/libs/commons-cli-1.2.jar b/Push/GCMCommand/libs/commons-cli-1.2.jar
new file mode 100644
index 000000000..ce4b9fffe
Binary files /dev/null and b/Push/GCMCommand/libs/commons-cli-1.2.jar differ
diff --git a/Push/GCMCommand/libs/gcm-server.jar b/Push/GCMCommand/libs/gcm-server.jar
new file mode 100644
index 000000000..983fdb7a7
Binary files /dev/null and b/Push/GCMCommand/libs/gcm-server.jar differ
diff --git a/Push/GCMCommand/libs/json_simple-1.1.jar b/Push/GCMCommand/libs/json_simple-1.1.jar
new file mode 100644
index 000000000..f395f4147
Binary files /dev/null and b/Push/GCMCommand/libs/json_simple-1.1.jar differ
diff --git a/Push/GCMCommand/src/com/commonsware/android/gcm/cmd/GCM.java b/Push/GCMCommand/src/com/commonsware/android/gcm/cmd/GCM.java
new file mode 100644
index 000000000..12d16d930
--- /dev/null
+++ b/Push/GCMCommand/src/com/commonsware/android/gcm/cmd/GCM.java
@@ -0,0 +1,126 @@
+/***
+ Copyright (c) 2012 CommonsWare, LLC
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy
+ of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
+ by applicable law or agreed to in writing, software distributed under the
+ License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+ OF ANY KIND, either express or implied. See the License for the specific
+ language governing permissions and limitations under the License.
+
+ From _The Busy Coder's Guide to Android Development_
+ http://commonsware.com/Android
+*/
+
+package com.commonsware.android.gcm.cmd;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+import com.google.android.gcm.server.Constants;
+import com.google.android.gcm.server.Message;
+import com.google.android.gcm.server.MulticastResult;
+import com.google.android.gcm.server.Result;
+import com.google.android.gcm.server.Sender;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.PosixParser;
+
+public class GCM {
+ @SuppressWarnings("static-access")
+ public static void main(String[] args) {
+ Option helpOpt=new Option("h", "help", false, "print this message");
+ Option apiKeyOpt=
+ OptionBuilder.withArgName("key").hasArg().isRequired()
+ .withDescription("GCM API key")
+ .withLongOpt("apiKey").create('a');
+ Option deviceOpt=
+ OptionBuilder.withArgName("regId").hasArg().isRequired()
+ .withDescription("device to send to")
+ .withLongOpt("device").create('d');
+ Option dataOpt=
+ OptionBuilder.withArgName("key=value").hasArgs(2)
+ .withDescription("datum to send")
+ .withValueSeparator().withLongOpt("data")
+ .create('D');
+
+ Options options=new Options();
+
+ options.addOption(apiKeyOpt);
+ options.addOption(deviceOpt);
+ options.addOption(dataOpt);
+ options.addOption(helpOpt);
+
+ CommandLineParser parser=new PosixParser();
+
+ try {
+ CommandLine line=parser.parse(options, args);
+
+ if (line.hasOption('h') || !line.hasOption('a')
+ || !line.hasOption('d')) {
+ HelpFormatter formatter=new HelpFormatter();
+ formatter.printHelp("gcm", options, true);
+ }
+ else {
+ sendMessage(line.getOptionValue('a'),
+ Arrays.asList(line.getOptionValues('d')),
+ line.getOptionProperties("data"));
+ }
+ }
+ catch (org.apache.commons.cli.MissingOptionException moe) {
+ System.err.println("Invalid command syntax");
+ HelpFormatter formatter=new HelpFormatter();
+ formatter.printHelp("gcm", options, true);
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static void sendMessage(String apiKey, List devices,
+ Properties data) throws Exception {
+ Sender sender=new Sender(apiKey);
+ Message.Builder builder=new Message.Builder();
+
+ for (Object o : data.keySet()) {
+ String key=o.toString();
+
+ builder.addData(key, data.getProperty(key));
+ }
+
+ MulticastResult mcResult=sender.send(builder.build(), devices, 5);
+
+ for (int i=0; i < mcResult.getTotal(); i++) {
+ Result result=mcResult.getResults().get(i);
+
+ if (result.getMessageId() != null) {
+ String canonicalRegId=result.getCanonicalRegistrationId();
+
+ if (canonicalRegId != null) {
+ System.err.println(String.format("%s canonical ID = %s",
+ devices.get(i),
+ canonicalRegId));
+ }
+ else {
+ System.out.println(String.format("%s success", devices.get(i)));
+ }
+ }
+ else {
+ String error=result.getErrorCodeName();
+
+ if (Constants.ERROR_NOT_REGISTERED.equals(error)) {
+ System.err.println(String.format("%s is unregistered",
+ devices.get(i)));
+ }
+ else if (error != null) {
+ System.err.println(String.format("%s error = %s",
+ devices.get(i), error));
+ }
+ }
+ }
+ }
+}