Android Cloud to Device Messaging (C2DM)
Android C2DM
This tutorial describes
how to push information from a server to the Google device. It is based on
Eclipse 3.7, Java 1.6 and Android 2.3 (Gingerbread).
Table of Contents
Most mobile apps require
data from the Internet. One approach for updating its data is that the apps
periodically polls a server for new data (Polling). If no new data is available
this approach uses unnecessary network bandwidth and consumes the battery of
the mobile phone.
An alternative approach
is that the server contacts the mobile app once new data is available (Push).
If the data does not change constantly, Push is the preferred solution.
As of Android 2.2 it is
possible to push information to an Android app. This service is called Cloud
to Device messaging or short C2DM.
In a C2DM you have three
involved parties. The application server which wants to push messages to the
Android device, Googles C2DM servers and the Android app. The program on the
application server can be written in any programming language, e.g. Java, PHP,
Python, etc.
When the application
server needs to push a message to the Android application, it sends the message
via an HTTP POST to Google’s C2DM servers.
The C2DM servers route
the message to the device. If the device is not online, the message will be
delivered once the device is available. Once the message is received, an
Broadcast Intent is created. The mobile app has registered an Intent Receiver
for this Broadcast. The app is started and processes the message via the
defined Intent Receiver.
C2DM messages are
limited in size to 1024 bytes and are intended to inform the device about new
data not to transfer it. The typical workflow is that Googles C2DM servers
notify the Android app that new data is available. Afterwards the Android app
fetches the data from a different server.
Android devices maintain
an connection to the Android Play server. C2DM uses this existing connections
to the Google servers. This connection is highly optimize to minimize bandwidth
and battery consumption.
C2DM is currently still
in beta and you need to apply to use it. C2DM applies a limit of approximately
200 000 messages per sender per day and is currently free of charge.
C2MD is available as of Android
2.2 and requires that Android Play application is installed on the device.
To use C2DM on the
Android simulator you also need to use a Google device with API 8 or higher and
to register with a Google account on the emulator via the Settings.
To use C2DM in your
application to have to register for the following permissions
- com.google.android.c2dm.permission.RECEIVE
- android.permission.INTERNET
Your application should
also declare the permission "applicationPackage +
".permission.C2D_MESSAGE" with the
"android:protectionLevel" of "signature" so that other
applications cannot register and receive message for the application.
android:protectionLevel="signature". ensures that applications with
request a permission must be signed with same certificate as the application
that declared the permission.
Your application must
register an intent receiver for the two intents:
- com.google.android.c2dm.intent.REGISTRATION
- com.google.android.c2dm.intent.RECEIVE
The receiver for
"com.google.android.c2dm.intent.RECEIVE" will be called once a new
message is received, while the receiver for
"com.google.android.c2dm.intent.REGISTRATION" will be called once the
registration code for the app is received.
The application server
needs to authenticate himself with the C2DM servers. Via an email and password
an authentication token is determined with an HTTP POST request to the C2DM
servers. The token is stored on the application server and is used to
authenticate the application server with the C2DM servers once he sends out
messages.
For example you can get
the token for an registered email and password via the following coding:
package tyagi.robin.java.c2dm.server.util;
import
java.io.BufferedReader;
import
java.io.IOException;
import
java.io.InputStreamReader;
import
java.io.OutputStream;
import
java.net.HttpURLConnection;
import
java.net.URL;
public class AuthenticationUtil
{
private AuthenticationUtil() {
// Util class cannot get
instanziated
}
public static String getToken(String email, String password)
throws IOException
{
// Create the post data
// Requires a field with the
email and the password
StringBuilder builder = new
StringBuilder();
builder.append("Email=").append(email);
builder.append("&Passwd=").append(password);
builder.append("&accountType=GOOGLE");
builder.append("&source=MyLittleExample");
builder.append("&service=ac2dm");
// Setup the Http Post
byte[] data =
builder.toString().getBytes();
URL url = new URL("https://www.google.com/accounts/ClientLogin");
HttpURLConnection con = (HttpURLConnection)
url.openConnection();
con.setUseCaches(false);
con.setDoOutput(true);
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
con.setRequestProperty("Content-Length",
Integer.toString(data.length));
// Issue the HTTP POST
request
OutputStream output = con.getOutputStream();
output.write(data);
output.close();
// Read the response
BufferedReader reader = new
BufferedReader(new InputStreamReader(con.getInputStream()));
String line = null;
String auth_key
= null;
while ((line =
reader.readLine()) != null) {
if
(line.startsWith("Auth=")) {
auth_key = line.substring(5);
}
}
// Finally get the
authentication token
// To something useful with
it
return auth_key;
}
}
The token is
periodically refreshed.
The above example is in
Java. It is also possible to get the token via other http tools or programming
languages. For example you can simulate the server via the command line tool
with the tool curl.
To register your Android
app for the C2DM service you fire an registration intent
"com.google.android.c2dm.intent.REGISTER". This will trigger a
service which will send the registration to the Google C2DM servers.
The intent include an
extra with the key "sender" and the email address which was
registered for the C2DM service. It also must include PendingIntent with the
"app" extra. The PendingIntent gives the Android system information
about the current application. The value for the "sender" is the
email address under which you registered your C2DM message service. Replace in
the following example "youruser@gmail.com" which your email address.
public void
register(View view) {
Intent intent = new Intent("com.google.android.c2dm.intent.REGISTER");
intent.putExtra("app",PendingIntent.getBroadcast(this, 0, new Intent(), 0));
intent.putExtra("sender", "youruser@gmail.com");
startService(intent);
}
The service will
asynchronously register with Google and will send the
"com.google.android.c2dm.intent.REGISTRATION" intent upon successful
registration. Your application need to register an Broadcast Receiver for this
intent. This also requires the usage of a permission based on your package as
the Android system checks this internally.
package tyagi.robin.android.c2dm.simpleclient;
import
android.content.BroadcastReceiver;
import
android.content.Context;
import
android.content.Intent;
import
android.util.Log;
public class
C2DMRegistrationReceiver extends BroadcastReceiver
{
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.w("C2DM", "Registration
Receiver called");
if ("com.google.android.c2dm.intent.REGISTRATION".equals(action))
{
Log.w("C2DM", "Received
registration ID");
final String
registrationId = intent
.getStringExtra("registration_id");
String error = intent.getStringExtra("error");
Log.d("C2DM", "dmControl:
registrationId = " + registrationId
+ ", error
= " + error);
// TODO Send this to my
application server
}
}
}
The
""AndroidManifest.xml looks like the following. Please note that if
you are using a different package that you have to adjust this coding.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="tyagi.robin.android.c2dm.simpleclient"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="8" />
<permission
android:name="tyagi.robin.android.c2dm.simpleclient.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission
android:name="tyagi.robin.android.c2dm.simpleclient.permission.C2D_MESSAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:icon="@drawable/icon"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".C2DMClientActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".C2DMRegistrationReceiver"
android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter >
<action android:name="com.google.android.c2dm.intent.REGISTRATION" >
</action>
<category android:name="tyagi.robin.android.c2dm.simpleclient" />
</intent-filter>
</receiver>
</application>
</manifest>
The
"com.google.android.c2dm.intent.REGISTRATION" intent includes a
registration ID. Each registration ID represents a particular device, e.g.
every Android phone will receive its own registration code.
The C2DM may refresh
this registration ID periodically but until a refreshment your application
should store this ID for later use.
After the Android app
received the registration ID it has to send this information to the application
server. The application server can use the registration ID to send a message to
the device via the C2DM servers from Google.
For example the
following code sends the deviceId and the registrationId to a server.
// Better do this in an
asynchronous thread
public void
sendRegistrationIdToServer(String deviceId, String registrationId) {
Log.d("C2DM", "Sending
registration ID to my application server");
HttpClient client = new
DefaultHttpClient();
HttpPost post = new HttpPost("http://your_url/register");
try {
List<NameValuePair> nameValuePairs = new
ArrayList<NameValuePair>(1);
// Get the deviceID
nameValuePairs.add(new
BasicNameValuePair("deviceid", deviceId));
nameValuePairs.add(new
BasicNameValuePair("registrationid", registrationId));
post.setEntity(new
UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = client.execute(post);
BufferedReader rd =
new
BufferedReader(new InputStreamReader(response.getEntity().getContent()));
String line = "";
while ((line =
rd.readLine()) != null) {
Log.e("HttpResponse", line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
The server uses some
persistence to store the registration IDs.
Similar to registration
of the registration receiver you need to configure a message receiver. This could
be the same or a different receiver then the registration receiver. The
following shows an example of an separate message receiver.
package tyagi.robin.android.c2dm.simpleclient;
import
android.content.BroadcastReceiver;
import
android.content.Context;
import
android.content.Intent;
import
android.util.Log;
public class
C2DMMessageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.w("C2DM", "Message
Receiver called");
if ("com.google.android.c2dm.intent.RECEIVE".equals(action))
{
Log.w("C2DM", "Received
message");
final String
payload = intent.getStringExtra("payload");
Log.d("C2DM", "dmControl:
payload = " + payload);
// Send this to my
application server
}
}
}
In addition you need to
register the message receiver in your AndroidManifest.xml file.
<receiver android:name=".C2DMMessageReceiver"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE"></action>
<category android:name="tyagi.robin.android.c2dm.simpleclient" />
</intent-filter>
</receiver>
At this point your
application server and your Android app are ready to use C2DM. Your server has
his authentication token and the registration ID of the app. And the mobile app
has registered Broadcast Receiver for receiving the message.
To send a message to a
device, the application server sends a HTTP POST request to the Google C2DM
servers. This HTTP GET request contains the registration ID for this device and
well as the authentication token (to tell Google that this server is allowed to
send messages).
package tyagi.robin.java.c2dm.server.util;
import
java.io.IOException;
import
java.io.OutputStream;
import
java.net.URL;
import
java.net.URLEncoder;
import
javax.net.ssl.HostnameVerifier;
import
javax.net.ssl.HttpsURLConnection;
import
javax.net.ssl.SSLSession;
public class MessageUtil
{
private final static String AUTH
= "authentication";
private static final String
UPDATE_CLIENT_AUTH = "Update-Client-Auth";
public static final String PARAM_REGISTRATION_ID = "registration_id";
public static final String PARAM_DELAY_WHILE_IDLE = "delay_while_idle";
public static final String PARAM_COLLAPSE_KEY = "collapse_key";
private static final String UTF8
= "UTF-8";
public static int sendMessage(String auth_token, String registrationId,
String message) throws IOException
{
StringBuilder postDataBuilder = new
StringBuilder();
postDataBuilder.append(PARAM_REGISTRATION_ID).append("=")
.append(registrationId);
postDataBuilder.append("&").append(PARAM_COLLAPSE_KEY).append("=")
.append("0");
postDataBuilder.append("&").append("data.payload").append("=")
.append(URLEncoder.encode(message,
UTF8));
byte[] postData =
postDataBuilder.toString().getBytes(UTF8);
// Hit the dm URL.
URL url = new URL("https://android.clients.google.com/c2dm/send");
HttpsURLConnection
.setDefaultHostnameVerifier(new
CustomizedHostnameVerifier());
HttpsURLConnection conn = (HttpsURLConnection)
url.openConnection();
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded;charset=UTF-8");
conn.setRequestProperty("Content-Length",
Integer.toString(postData.length));
conn.setRequestProperty("Authorization", "GoogleLogin
auth="
+ auth_token);
OutputStream out = conn.getOutputStream();
out.write(postData);
out.close();
int responseCode
= conn.getResponseCode();
return
responseCode;
}
private static class
CustomizedHostnameVerifier implements HostnameVerifier
{
public boolean
verify(String hostname, SSLSession session) {
return true;
}
}
}
Once your server sends
the message to the C2DM server this server will queue the message until the
device is available. The message will be send to the device as a broadcast.
Your application needs to register for this broadcast event to receive the message.
A received message is
send to the registered broadcast receiver for
"com.google.android.c2dm.intent.RECEIVE". The data can be received
from the Intent via getExtras(). The available keys are "payload",
"from", "collapse_key". The actual data is included in
"payload". The receiver can extracts this data and can react to it.
Currently C2DM is under
beta testing. You need to ask for access. Here is the link to the signup form.
If you test the
following example on the emulator make sure that you use Google API 8 or later.
You also have to register a Google user on the virtual device under Settings → Accounts
Sync.
If you test the
following example on a real device make sure that the Android Market is
installed.
Create the Android
project "tyagi.robin.android.c2dm.simpleclient" with the activity
"C2DMClientActivity". Create the following layout main.xml.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="register"
android:text="Register" >
</Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="showRegistrationId"
android:text="Show" >
</Button>
</LinearLayout>
Also create the layout
"activity_result.xml" which we will use in our result activities.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/result"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:text="No
info."
android:textAppearance="?android:attr/textAppearanceLarge" >
</TextView>
</LinearLayout>
Create the following
class "C2DMReceiverReceiver" and "C2DMMessageReceiver"
which we will later registers as receivers for the registration intent and the
message intent.
package tyagi.robin.android.c2dm.simpleclient;
import
java.io.BufferedReader;
import
java.io.IOException;
import
java.io.InputStreamReader;
import
java.util.ArrayList;
import
java.util.List;
import
org.apache.http.HttpResponse;
import
org.apache.http.NameValuePair;
import
org.apache.http.client.HttpClient;
import
org.apache.http.client.entity.UrlEncodedFormEntity;
import
org.apache.http.client.methods.HttpPost;
import
org.apache.http.impl.client.DefaultHttpClient;
import
org.apache.http.message.BasicNameValuePair;
import android.app.Notification;
import
android.app.NotificationManager;
import
android.app.PendingIntent;
import
android.content.BroadcastReceiver;
import
android.content.Context;
import
android.content.Intent;
import
android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import
android.preference.PreferenceManager;
import
android.provider.Settings.Secure;
import
android.util.Log;
public class
C2DMRegistrationReceiver extends
BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.w("C2DM", "Registration
Receiver called");
if ("com.google.android.c2dm.intent.REGISTRATION".equals(action))
{
Log.w("C2DM", "Received
registration ID");
final String
registrationId = intent
.getStringExtra("registration_id");
String error = intent.getStringExtra("error");
Log.d("C2DM", "dmControl:
registrationId = " + registrationId
+ ", error
= " + error);
String deviceId =
Secure.getString(context.getContentResolver(),
Secure.ANDROID_ID);
createNotification(context, registrationId);
sendRegistrationIdToServer(deviceId,
registrationId);
// Also save it in the
preference to be able to show it later
saveRegistrationId(context, registrationId);
}
}
private void
saveRegistrationId(Context context, String registrationId) {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
Editor edit = prefs.edit();
edit.putString(C2DMClientActivity.AUTH,
registrationId);
edit.commit();
}
public void createNotification(Context context, String registrationId) {
NotificationManager notificationManager =
(NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new
Notification(R.drawable.icon,
"Registration
successful", System.currentTimeMillis());
// Hide the notification
after its selected
notification.flags |= Notification.FLAG_AUTO_CANCEL;
Intent intent = new
Intent(context, RegistrationResultActivity.class);
intent.putExtra("registration_id",
registrationId);
PendingIntent pendingIntent =
PendingIntent.getActivity(context, 0,
intent, 0);
notification.setLatestEventInfo(context, "Registration",
"Successfully
registered", pendingIntent);
notificationManager.notify(0, notification);
}
// Incorrect usage as the receiver may be canceled at any time
// do this in an service and in an own thread
public void sendRegistrationIdToServer(String deviceId,
String registrationId) {
Log.d("C2DM", "Sending
registration ID to my application server");
HttpClient client = new
DefaultHttpClient();
HttpPost post = new HttpPost("http://vogellac2dm.appspot.com/register");
try {
List<NameValuePair> nameValuePairs = new
ArrayList<NameValuePair>(1);
// Get the deviceID
nameValuePairs.add(new
BasicNameValuePair("deviceid", deviceId));
nameValuePairs.add(new
BasicNameValuePair("registrationid",
registrationId));
post.setEntity(new
UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = client.execute(post);
BufferedReader rd = new
BufferedReader(new InputStreamReader(response.getEntity().getContent()));
String line = "";
while ((line =
rd.readLine()) != null) {
Log.e("HttpResponse", line);
}
} catch (IOException
e) {
e.printStackTrace();
}
}
}
package tyagi.robin.android.c2dm.simpleclient;
import
android.app.Notification;
import
android.app.NotificationManager;
import android.app.PendingIntent;
import
android.content.BroadcastReceiver;
import
android.content.Context;
import
android.content.Intent;
import
android.util.Log;
public class
C2DMMessageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.w("C2DM", "Message
Receiver called");
if ("com.google.android.c2dm.intent.RECEIVE".equals(action))
{
Log.w("C2DM", "Received
message");
final String
payload = intent.getStringExtra("payload");
Log.d("C2DM", "dmControl:
payload = " + payload);
// TODO Send this to my
application server to get the real data
// Lets make something
visible to show that we received the message
createNotification(context, payload);
}
}
public void createNotification(Context context, String payload) {
NotificationManager notificationManager =
(NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new
Notification(R.drawable.icon,
"Message received",
System.currentTimeMillis());
// Hide the notification
after its selected
notification.flags |= Notification.FLAG_AUTO_CANCEL;
Intent intent = new
Intent(context, MessageReceivedActivity.class);
intent.putExtra("payload", payload);
PendingIntent pendingIntent =
PendingIntent.getActivity(context, 0,
intent, 0);
notification.setLatestEventInfo(context, "Message",
"New message received",
pendingIntent);
notificationManager.notify(0, notification);
}
}
Also create the
following two Activities which we will use to see the results.
package tyagi.robin.android.c2dm.simpleclient;
import
android.app.Activity;
import
android.os.Bundle;
import
android.widget.TextView;
public class
RegistrationResultActivity extends Activity {
@Override
protected void
onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_result);
Bundle extras = getIntent().getExtras();
if (extras !=
null) {
String registrationId = extras.getString("registration_id");
if
(registrationId != null && registrationId.length() > 0) {
TextView view = (TextView)
findViewById(R.id.result);
view.setText(registrationId);
}
}
super.onCreate(savedInstanceState);
}
}
package tyagi.robin.android.c2dm.simpleclient;
import
android.app.Activity;
import
android.os.Bundle;
import
android.widget.TextView;
public class
MessageReceivedActivity extends Activity {
@Override
protected void onCreate(Bundle
savedInstanceState) {
setContentView(R.layout.activity_result);
Bundle extras = getIntent().getExtras();
if (extras !=
null) {
String message = extras.getString("payload");
if (message !=
null && message.length() > 0) {
TextView view = (TextView)
findViewById(R.id.result);
view.setText(message);
}
}
super.onCreate(savedInstanceState);
}
}
Create the following AndroidManifest.xml file. That will register the Intent
Receivers, Activities and requests the required permissions.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="tyagi.robin.android.c2dm.simpleclient"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="8" />
<permission
android:name="tyagi.robin.android.c2dm.simpleclient.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission
android:name="tyagi.robin.android.c2dm.simpleclient.permission.C2D_MESSAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:icon="@drawable/icon"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".C2DMClientActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".C2DMRegistrationReceiver"
android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter >
<action android:name="com.google.android.c2dm.intent.REGISTRATION" >
</action>
<category android:name="tyagi.robin.android.c2dm.simpleclient" />
</intent-filter>
</receiver>
<receiver
android:name=".C2DMMessageReceiver"
android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter >
<action android:name="com.google.android.c2dm.intent.RECEIVE" >
</action>
<category android:name="tyagi.robin.android.c2dm.simpleclient" />
</intent-filter>
</receiver>
<activity android:name="RegistrationResultActivity" >
</activity>
<activity android:name="MessageReceivedActivity" >
</activity>
</application>
</manifest>
Change your
"C2DMClientActivity" class to the following.
package tyagi.robin.android.c2dm.simpleclient;
import android.app.Activity;
import
android.app.PendingIntent;
import
android.content.Intent;
import
android.content.SharedPreferences;
import
android.os.Bundle;
import
android.preference.PreferenceManager;
import
android.util.Log;
import
android.view.View;
import
android.widget.Toast;
public class
C2DMClientActivity extends Activity {
public final static String AUTH = "authentication";
// Example Activity to trigger a request for a registration ID
to the Google
// server
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public void register(View view) {
Log.w("C2DM", "start
registration process");
Intent intent = new Intent("com.google.android.c2dm.intent.REGISTER");
intent.putExtra("app",
PendingIntent.getBroadcast(this, 0, new Intent(), 0));
// Sender currently not used
intent.putExtra("sender", "nonsenses@gmail.com");
startService(intent);
}
public void showRegistrationId(View view) {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(this);
String string = prefs.getString(AUTH, "n/a");
Toast.makeText(this, string,
Toast.LENGTH_LONG).show();
Log.d("C2DM RegId", string);
}
}
The methods in the
activity are connected to the buttons via the onclick property of the buttons.
The first button triggers a request for a registration ID and the second button
shows the saved registration key. Both also write the registration id also to
the Logcat View.
Copy the registration Id
from the Logcat View so that you can later use it in your server
implementation.
Run your application,
maintain your registered user and press the button. Check LogCat for the
registration ID.
If you see the following
message:
Unable to start service Intent
{act=com.google.android.c2dm.intent.REGISTER ... }: not found
make sure you are using
a Google device and that you have a Google user registered on the device.
If you run a Linux
system you can easily test the service on the command line. You can request
your authentication key via curl on the
command line. From the response get the part after "Auth=".
curl https://www.google.com/accounts/ClientLogin
-d
Email=your_user -d "Passwd=your_password" -d
accountType=GOOGLE
-d source=Google-cURL-Example -d service=ac2dm
This part and the
registration code can be used to send a message to your device.
curl --header "Authorization:
GoogleLogin auth=your_authenticationid"
"https://android.apis.google.com/c2dm/send" -d
registration_id=your_registration
-d "data.payload=payload" -d collapse_key=0
As described earlier the
application server needs to get an authentication key via HTTPS. Afterwards it
can send messages to the device via HTTP by supplying the authentication key
and the registration ID.
We will simulate the
Server via a Java program. The registration ID of the device will be hard-coded
into this app as we do not have the possibility to reach this app via http.
Keep in mind that this is only an example to make it simple to test C2DM.
To store you credentials
use the following class.
package tyagi.robin.java.c2dm.server.secret;
public class
SecureStorage {
public static final String USER = "your_registeredUser";
public static final String PASSWORD = "your_password";
}
Create a new Java
project "tyagi.robin.java.c2dm.server". Create the following class.
This class is an utility class to get the authentication token from the Google
server.
package tyagi.robin.java.c2dm.server.util;
import
java.io.BufferedReader;
import
java.io.IOException;
import
java.io.InputStreamReader;
import
java.io.OutputStream;
import
java.net.HttpURLConnection;
import
java.net.URL;
public class
AuthenticationUtil {
private AuthenticationUtil() {
// Util class cannot get
instanziated
}
public static String getToken(String email, String password)
throws IOException
{
// Create the post data
// Requires a field with the
email and the password
StringBuilder builder = new
StringBuilder();
builder.append("Email=").append(email);
builder.append("&Passwd=").append(password);
builder.append("&accountType=GOOGLE");
builder.append("&source=MyLittleExample");
builder.append("&service=ac2dm");
// Setup the Http Post
byte[] data =
builder.toString().getBytes();
URL url = new URL("https://www.google.com/accounts/ClientLogin");
HttpURLConnection con = (HttpURLConnection)
url.openConnection();
con.setUseCaches(false);
con.setDoOutput(true);
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
con.setRequestProperty("Content-Length",
Integer.toString(data.length));
// Issue the HTTP POST
request
OutputStream output = con.getOutputStream();
output.write(data);
output.close();
// Read the response
BufferedReader reader = new
BufferedReader(new InputStreamReader(con.getInputStream()));
String line = null;
String auth_key = null;
while ((line =
reader.readLine()) != null) {
if
(line.startsWith("Auth=")) {
auth_key = line.substring(5);
}
}
// Finally get the
authentication token
// To something useful with
it
return auth_key;
}
}
Create the following
class "GetAuthenticationToken". This class can be used to get the
authentication token.
package tyagi.robin.java.c2dm.server;
import
java.io.IOException;
import tyagi.robin.java.c2dm.server.secret.SecureStorage;
import tyagi.robin.java.c2dm.server.util.AuthenticationUtil;
public class
GetAuthenticationToken {
public static void main(String[] args) throws IOException
{
String token =
AuthenticationUtil.getToken(SecureStorage.USER,
SecureStorage.PASSWORD);
System.out.println(token);
}
}
Run your
GetAuthenticationToken class and copy the authentication token from the command
line.
Create the following
class and maintain your authentication token and your registration id.
package tyagi.robin.java.c2dm.server;
public class
ServerConfiguration {
public static final String AUTHENTICATION_TOKEN = "your_token";
public static final String REGISTRATION_ID = "registration_id_of_your_device";
}
Also create the
following utility class which will allow to send messages to your device.
package tyagi.robin.java.c2dm.server.util;
import
java.io.IOException;
import
java.io.OutputStream;
import
java.net.URL;
import
java.net.URLEncoder;
import
javax.net.ssl.HostnameVerifier;
import
javax.net.ssl.HttpsURLConnection;
import
javax.net.ssl.SSLSession;
public class MessageUtil
{
private final static String AUTH
= "authentication";
private static final String
UPDATE_CLIENT_AUTH = "Update-Client-Auth";
public static final String PARAM_REGISTRATION_ID = "registration_id";
public static final String PARAM_DELAY_WHILE_IDLE = "delay_while_idle";
public static final String PARAM_COLLAPSE_KEY = "collapse_key";
private static final String UTF8
= "UTF-8";
public static int sendMessage(String auth_token, String registrationId,
String message) throws IOException
{
StringBuilder postDataBuilder = new
StringBuilder();
postDataBuilder.append(PARAM_REGISTRATION_ID).append("=")
.append(registrationId);
postDataBuilder.append("&").append(PARAM_COLLAPSE_KEY).append("=")
.append("0");
postDataBuilder.append("&").append("data.payload").append("=")
.append(URLEncoder.encode(message,
UTF8));
byte[] postData =
postDataBuilder.toString().getBytes(UTF8);
// Hit the dm URL.
URL url = new URL("https://android.clients.google.com/c2dm/send");
HttpsURLConnection
.setDefaultHostnameVerifier(new
CustomizedHostnameVerifier());
HttpsURLConnection conn = (HttpsURLConnection)
url.openConnection();
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded;charset=UTF-8");
conn.setRequestProperty("Content-Length",
Integer.toString(postData.length));
conn.setRequestProperty("Authorization", "GoogleLogin
auth="
+ auth_token);
OutputStream out = conn.getOutputStream();
out.write(postData);
out.close();
int responseCode
= conn.getResponseCode();
return
responseCode;
}
private static class
CustomizedHostnameVerifier implements HostnameVerifier
{
public boolean
verify(String hostname, SSLSession session) {
return true;
}
}
}
Finally create the
following class "SendMessageToDevice" which can send messages to your
device.
package tyagi.robin.java.c2dm.server;
import java.io.IOException;
import tyagi.robin.java.c2dm.server.util.MessageUtil;
public class
SendMessageToDevice {
public static void main(String[] args) throws IOException
{
// "Message to your
device." is the message we will send to the Android app
int responseCode
= MessageUtil.sendMessage(ServerConfiguration.AUTHENTICATION_TOKEN,
ServerConfiguration.REGISTRATION_ID, "Message
to your device.");
System.out.println(responseCode);
}
}
Run it. This should send
a message to your device and give you the return code "200". On your
device you should see a notification and if you open it you should see the
message displayed.
Comments
Post a Comment