1. Cloud to device messaging
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.
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.
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 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
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.
-
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:
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.
-
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:
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.
For example you can get the token for an registered email and password via the following coding:
package de.vogella.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.
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.
The ""AndroidManifest.xml looks like the following. Please note that if you are using a different package that you have to adjust this coding.
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.
The server uses some persistence to store the registration IDs.
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 de.vogella.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="de.vogella.android.c2dm.simpleclient" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" /> <permission android:name="de.vogella.android.c2dm.simpleclient.permission.C2D_MESSAGE" android:protectionLevel="signature" /> <uses-permission android:name="de.vogella.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="de.vogella.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.
In addition you need to register the message receiver in your
package de.vogella.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="de.vogella.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).
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.
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 → .
If you test the following example on a real device make sure that the Android Market is installed.
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 de.vogella.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.
3. Device and Registration
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 → .
If you test the following example on a real device make sure that the Android Market is installed.
Create the Android project
"de.vogella.android.c2dm.simpleclient" with
the
activity
"C2DMClientActivity". Create the following layout
Also create the layout "activity_result.xml" which we will use in our result activities.
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.
Also create the following two Activities which we will use to see the results.
Create the following
Change your "C2DMClientActivity" class to the following.
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.
package de.vogella.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 de.vogella.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 de.vogella.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 de.vogella.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="de.vogella.android.c2dm.simpleclient" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" /> <permission android:name="de.vogella.android.c2dm.simpleclient.permission.C2D_MESSAGE" android:protectionLevel="signature" /> <uses-permission android:name="de.vogella.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="de.vogella.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="de.vogella.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 de.vogella.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:
make sure you are using a Google device and that you have a Google user registered on the device.
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=".
This part and the registration code can be used to send a message to your device.
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.
Create a new Java project "de.vogella.java.c2dm.server". Create the following class. This class is an utility class to get the authentication token from the Google server.
Create the following class "GetAuthenticationToken". This class can be used to get the authentication 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.
Also create the following utility class which will allow to send messages to your device.
Finally create the following class "SendMessageToDevice" which can send messages to your device.
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.
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 de.vogella.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 "de.vogella.java.c2dm.server". Create the following class. This class is an utility class to get the authentication token from the Google server.
package de.vogella.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 de.vogella.java.c2dm.server; import java.io.IOException; import de.vogella.java.c2dm.server.secret.SecureStorage; import de.vogella.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 de.vogella.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 de.vogella.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 de.vogella.java.c2dm.server; import java.io.IOException; import de.vogella.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.
(vogella)