Version 0.8
Copyright © 2011 Lars Vogel
11.03.2011
Revision History | ||
---|---|---|
Revision 0.1 | 08.01.2011 | Lars Vogel |
Created | ||
Revision 0.2 - 0.8 | 09.01.2011 - 11.03.2011 | Lars Vogel |
updated and bugfixes |
Table of Contents
As of Android 2.2 the Android platform allows to push information from the Internet to an Android phone. This service is called "Cloud to Device messaging" or short C2DM.
The standard mechanism for an application which requires data from the internet is to poll periodically for the data. This uses unnecessary network bandwidth and consumes the battery. Therefore pushing data over the internet to the phone might be a preferable solution.
C2DM is intented to notify the mobile phone that new data is available. It is not intended to send lots of data, as the size of the message is limited to 1024 characters. The push service would notify the mobile device and the Android application would fetch the data from somewhere else.
C2DM uses existing connections the Google servers which is highly optimize to minimize bandwidth and battery consumption.
The standard mechanism for an application which requires data from the internet is to poll periodically for the data. This uses unnecessary network bandwidth and consumes the battery. Therefore pushing data over the internet to the phone might be a preferable solution.
C2DM is intented to notify the mobile phone that new data is available. It is not intended to send lots of data, as the size of the message is limited to 1024 characters. The push service would notify the mobile device and the Android application would fetch the data from somewhere else.
C2DM uses existing connections the Google servers which is highly optimize to minimize bandwidth and battery consumption.
C2MD only works as of Android 2.2 and requires that Android Market 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 your Android device you have to write an Android application. This application will register at a Google server to be able to receive push messages from the Google server. Your server registers with the Google server and authenticates himself. The server can then push messages to the Google server which in return pushes the information to the device. For this authentication you require a Google user.
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 your Android device you have to write an Android application. This application will register at a Google server to be able to receive push messages from the Google server. Your server registers with the Google server and authenticates himself. The server can then push messages to the Google server which in return pushes the information to the device. For this authentication you require a Google user.
In a C2DM you have three involved parties. The server which wants to push messages to the Android device (application server), the Google C2DM servers and the Android application.
To enable C2DM for an Android application, this application must registers with the Google C2DM servers. After registration it receives a registration ID from the Google C2DM servers.
The Android application must then send the registration ID to its application server. The application server stores the registration ID. This registration ID is required to send the information later to this specific device.
When the application server needs to push a message to the Android applicatoin, it sends the message via HTTP GET to Google’s C2DM servers.
The C2DM servers route the message to the device once the device is available, and an Intent broadcast is sent to the Android application. The app is started and processes the message in its defined Intent Receiver.
To enable C2DM for an Android application, this application must registers with the Google C2DM servers. After registration it receives a registration ID from the Google C2DM servers.
The Android application must then send the registration ID to its application server. The application server stores the registration ID. This registration ID is required to send the information later to this specific device.
When the application server needs to push a message to the Android applicatoin, it sends the message via HTTP GET to Google’s C2DM servers.
The C2DM servers route the message to the device once the device is available, and an Intent broadcast is sent to the Android application. The app is started and processes the message in its defined Intent Receiver.
To register your application for the C2DM service your application needs to fire an registration intent "com.google.android.c2dm.intent.REGISTER" to the C2DM server. This intent includes a Google mail address and the application ID.
Upon successful registration the C2DM server broadcast a REGISTRATION Intent to the application which 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.
Google provides a small set of classes which makes the registration as smooth as possible.
After the Android application received its unique registration ID it has to send this information to the application server.The application server can use this registration ID to send messages to the Android application on the specific device.
Upon successful registration the C2DM server broadcast a REGISTRATION Intent to the application which 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.
Google provides a small set of classes which makes the registration as smooth as possible.
After the Android application received its unique registration ID it has to send this information to the application server.The application server can use this registration ID to send messages to the Android application on the specific device.
With your registration ID the application server can send messages to your device. This requires in addition an authorization token which needs to be generated for each per Android application and registration ID.
The authorization token is generated by sending a post HTTPS request to the C2DM servers.
To send a message to the Android application on a specific device the application server sends a HTTP GET request to the Google C2DM servers. This HTTP GET request contain the registration ID (to address a specific 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.
The authorization token is generated by sending a post HTTPS request to the C2DM servers.
To send a message to the Android application on a specific device the application server sends a HTTP GET request to the Google C2DM servers. This HTTP GET request contain the registration ID (to address a specific 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.
If a message is received on a device via a broadcast event. The system extracts the raw key/value pairs from the message. It then passes the key/value pairs to the Android application in a "com.google.android.c2dm.intent.RECEIVE" Intent. 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 Android application extracts this data and can react to it.
To use C2DM in your application to have to register for the permission "com.google.android.c2dm.permission.RECEIVE" and in most cases also "android.permission.INTERNET" permission. 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 also register as an intent receiver for the two intents "com.google.android.c2dm.intent.RECEIVE" and "com.google.android.c2dm.intent.REGISTRATION". "com.google.android.c2dm.SEND"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.
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 "de.vogella.android.c2dm" with the activity "RegisterActivity". Create the following layout "main.xml" which will allow to enter your registered user id for C2DM and send a registration request to the C2DM servers.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <EditText android:id="@+id/editText1" android:layout_height="wrap_content" android:layout_width="match_parent" android:hint="Enter C2DM registered User" ></EditText> <LinearLayout android:layout_height="match_parent" android:layout_width="match_parent" android:id="@+id/linearLayout1" android:gravity="center"> <Button android:id="@+id/button1" android:text="Register for Push Notification" android:layout_height="wrap_content" android:onClick="register" android:layout_width="wrap_content"></Button> </LinearLayout> </LinearLayout>
Create the following three classes from Google. Unfortunately I was not able to find a downloadable package of these classes at Google therefore I reproduce them.
/* * Copyright 2010 Google Inc. * * 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. */ package com.google.android.c2dm; import java.io.IOException; import android.app.AlarmManager; import android.app.IntentService; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.PowerManager; import android.util.Log; /** * Base class for C2D message receiver. Includes constants for the strings used * in the protocol. */ public abstract class C2DMBaseReceiver extends IntentService { private static final String C2DM_RETRY = "com.google.android.c2dm.intent.RETRY"; public static final String REGISTRATION_CALLBACK_INTENT = "com.google.android.c2dm.intent.REGISTRATION"; private static final String C2DM_INTENT = "com.google.android.c2dm.intent.RECEIVE"; // Logging tag private static final String TAG = "C2DM"; // Extras in the registration callback intents. public static final String EXTRA_UNREGISTERED = "unregistered"; public static final String EXTRA_ERROR = "error"; public static final String EXTRA_REGISTRATION_ID = "registration_id"; public static final String ERR_SERVICE_NOT_AVAILABLE = "SERVICE_NOT_AVAILABLE"; public static final String ERR_ACCOUNT_MISSING = "ACCOUNT_MISSING"; public static final String ERR_AUTHENTICATION_FAILED = "AUTHENTICATION_FAILED"; public static final String ERR_TOO_MANY_REGISTRATIONS = "TOO_MANY_REGISTRATIONS"; public static final String ERR_INVALID_PARAMETERS = "INVALID_PARAMETERS"; public static final String ERR_INVALID_SENDER = "INVALID_SENDER"; public static final String ERR_PHONE_REGISTRATION_ERROR = "PHONE_REGISTRATION_ERROR"; // wakelock private static final String WAKELOCK_KEY = "C2DM_LIB"; private static PowerManager.WakeLock mWakeLock; private final String senderId; /** * The C2DMReceiver class must create a no-arg constructor and pass the * sender id to be used for registration. */ public C2DMBaseReceiver(String senderId) { // senderId is used as base name for threads, etc. super(senderId); this.senderId = senderId; } /** * Called when a cloud message has been received. */ protected abstract void onMessage(Context context, Intent intent); /** * Called on registration error. Override to provide better error messages. * * This is called in the context of a Service - no dialog or UI. */ public abstract void onError(Context context, String errorId); /** * Called when a registration token has been received. */ public void onRegistered(Context context, String registrationId) throws IOException { // registrationId will also be saved } /** * Called when the device has been unregistered. */ public void onUnregistered(Context context) { } @Override public final void onHandleIntent(Intent intent) { try { Context context = getApplicationContext(); if (intent.getAction().equals(REGISTRATION_CALLBACK_INTENT)) { handleRegistration(context, intent); } else if (intent.getAction().equals(C2DM_INTENT)) { onMessage(context, intent); } else if (intent.getAction().equals(C2DM_RETRY)) { C2DMessaging.register(context, senderId); } } finally { // Release the power lock, so phone can get back to sleep. // The lock is reference counted by default, so multiple // messages are ok. // If the onMessage() needs to spawn a thread or do something else, // it should use it's own lock. mWakeLock.release(); } } /** * Called from the broadcast receiver. Will process the received intent, * call handleMessage(), registered(), etc. in background threads, with a * wake lock, while keeping the service alive. */ static void runIntentInService(Context context, Intent intent) { if (mWakeLock == null) { // This is called from BroadcastReceiver, there is no init. PowerManager pm = (PowerManager) context .getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); } mWakeLock.acquire(); // Use a naming convention, similar with how permissions and intents are // used. Alternatives are introspection or an ugly use of statics. String receiver = context.getPackageName() + ".C2DMReceiver"; intent.setClassName(context, receiver); context.startService(intent); } private void handleRegistration(final Context context, Intent intent) { final String registrationId = intent .getStringExtra(EXTRA_REGISTRATION_ID); String error = intent.getStringExtra(EXTRA_ERROR); String removed = intent.getStringExtra(EXTRA_UNREGISTERED); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "dmControl: registrationId = " + registrationId + ", error = " + error + ", removed = " + removed); } if (removed != null) { // Remember we are unregistered C2DMessaging.clearRegistrationId(context); onUnregistered(context); return; } else if (error != null) { // we are not registered, can try again C2DMessaging.clearRegistrationId(context); // Registration failed Log.e(TAG, "Registration error " + error); onError(context, error); if ("SERVICE_NOT_AVAILABLE".equals(error)) { long backoffTimeMs = C2DMessaging.getBackoff(context); Log.d(TAG, "Scheduling registration retry, backoff = " + backoffTimeMs); Intent retryIntent = new Intent(C2DM_RETRY); PendingIntent retryPIntent = PendingIntent .getBroadcast(context, 0 /* requestCode */, retryIntent, 0 /* flags */); AlarmManager am = (AlarmManager) context .getSystemService(Context.ALARM_SERVICE); am.set(AlarmManager.ELAPSED_REALTIME, backoffTimeMs, retryPIntent); // Next retry should wait longer. backoffTimeMs *= 2; C2DMessaging.setBackoff(context, backoffTimeMs); } } else { try { onRegistered(context, registrationId); C2DMessaging.setRegistrationId(context, registrationId); } catch (IOException ex) { Log.e(TAG, "Registration error " + ex.getMessage()); } } } }
package com.google.android.c2dm; /* */ import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; /** * Helper class to handle BroadcastReciver behavior. * - can only run for a limited amount of time - it must start a real service * for longer activity * - must get the power lock, must make sure it's released when all done. * */ public class C2DMBroadcastReceiver extends BroadcastReceiver { @Override public final void onReceive(Context context, Intent intent) { // To keep things in one place. C2DMBaseReceiver.runIntentInService(context, intent); setResult(Activity.RESULT_OK, null /* data */, null /* extra */); } }
package com.google.android.c2dm; /* * Copyright 2010 Google Inc. * * 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. */ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; /** * Utilities for device registration. * * Will keep track of the registration token in a private preference. */ public class C2DMessaging { public static final String EXTRA_SENDER = "sender"; public static final String EXTRA_APPLICATION_PENDING_INTENT = "app"; public static final String REQUEST_UNREGISTRATION_INTENT = "com.google.android.c2dm.intent.UNREGISTER"; public static final String REQUEST_REGISTRATION_INTENT = "com.google.android.c2dm.intent.REGISTER"; public static final String LAST_REGISTRATION_CHANGE = "last_registration_change"; public static final String BACKOFF = "backoff"; public static final String GSF_PACKAGE = "com.google.android.gsf"; // package static final String PREFERENCE = "com.google.android.c2dm"; private static final long DEFAULT_BACKOFF = 30000; /** * Initiate c2d messaging registration for the current application */ public static void register(Context context, String senderId) { Intent registrationIntent = new Intent(REQUEST_REGISTRATION_INTENT); registrationIntent.setPackage(GSF_PACKAGE); registrationIntent.putExtra(EXTRA_APPLICATION_PENDING_INTENT, PendingIntent.getBroadcast(context, 0, new Intent(), 0)); registrationIntent.putExtra(EXTRA_SENDER, senderId); context.startService(registrationIntent); // TODO: if intent not found, notification on need to have GSF } /** * Unregister the application. New messages will be blocked by server. */ public static void unregister(Context context) { Intent regIntent = new Intent(REQUEST_UNREGISTRATION_INTENT); regIntent.setPackage(GSF_PACKAGE); regIntent.putExtra(EXTRA_APPLICATION_PENDING_INTENT, PendingIntent.getBroadcast(context, 0, new Intent(), 0)); context.startService(regIntent); } /** * Return the current registration id. * * If result is empty, the registration has failed. * * @return registration id, or empty string if the registration is not complete. */ public static String getRegistrationId(Context context) { final SharedPreferences prefs = context.getSharedPreferences( PREFERENCE, Context.MODE_PRIVATE); String registrationId = prefs.getString("dm_registration", ""); return registrationId; } public static long getLastRegistrationChange(Context context) { final SharedPreferences prefs = context.getSharedPreferences( PREFERENCE, Context.MODE_PRIVATE); return prefs.getLong(LAST_REGISTRATION_CHANGE, 0); } static long getBackoff(Context context) { final SharedPreferences prefs = context.getSharedPreferences( PREFERENCE, Context.MODE_PRIVATE); return prefs.getLong(BACKOFF, DEFAULT_BACKOFF); } static void setBackoff(Context context, long backoff) { final SharedPreferences prefs = context.getSharedPreferences( PREFERENCE, Context.MODE_PRIVATE); Editor editor = prefs.edit(); editor.putLong(BACKOFF, backoff); editor.commit(); } // package static void clearRegistrationId(Context context) { final SharedPreferences prefs = context.getSharedPreferences( PREFERENCE, Context.MODE_PRIVATE); Editor editor = prefs.edit(); editor.putString("dm_registration", ""); editor.putLong(LAST_REGISTRATION_CHANGE, System.currentTimeMillis()); editor.commit(); } // package static void setRegistrationId(Context context, String registrationId) { final SharedPreferences prefs = context.getSharedPreferences( PREFERENCE, Context.MODE_PRIVATE); Editor editor = prefs.edit(); editor.putString("dm_registration", registrationId); editor.commit(); } }
Create the following class "C2DMReceiver" which we will later register as a service.
Create the following "AndroidManifest.xml". That will register the right receivers and requests the right permissions.
Change your Activity to the following. In this activity you can enter your registered Google C2DM user and ask for a registration key.
package de.vogella.android.c2dm; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.util.Log; import com.google.android.c2dm.C2DMBaseReceiver; public class C2DMReceiver extends C2DMBaseReceiver { public C2DMReceiver() { // Email address currently not used by the C2DM Messaging framework super("dummy@google.com"); } @Override public void onRegistered(Context context, String registrationId) throws java.io.IOException { // The registrationId should be send to your applicatioin server. // We just log it to the LogCat view // We will copy it from there Log.e("C2DM", "Registration ID arrived: Fantastic!!!"); Log.e("C2DM", registrationId); }; @Override protected void onMessage(Context context, Intent intent) { Log.e("C2DM", "Message: Fantastic!!!"); // Extract the payload from the message Bundle extras = intent.getExtras(); if (extras != null) { System.out.println(extras.get("payload")); // Now do something smart based on the information } } @Override public void onError(Context context, String errorId) { Log.e("C2DM", "Error occured!!!"); } }
Create the following "AndroidManifest.xml". That will register the right receivers and requests the right permissions.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="de.vogella.android.c2dm" android:versionCode="1" android:versionName="1.0"> <permission android:name="de.vogella.android.c2dm.permission.C2D_MESSAGE" android:protectionLevel="signature" /> <uses-permission android:name="de.vogella.android.c2dm.permission.C2D_MESSAGE" /> <!-- Permissions --> <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name="RegisterActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".C2DMReceiver" /> <!-- Only C2DM servers can send messages for the app. If permission is not set - any other app can generate it --> <receiver android:name="com.google.android.c2dm.C2DMBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND"> <!-- Receive the actual message --> <intent-filter> <action android:name="com.google.android.c2dm.intent.RECEIVE" /> <category android:name="de.vogella.android.c2dm" /> </intent-filter> <!-- Receive the registration id --> <intent-filter> <action android:name="com.google.android.c2dm.intent.REGISTRATION" /> <category android:name="de.vogella.android.c2dm" /> </intent-filter> </receiver> </application> </manifest>
Change your Activity to the following. In this activity you can enter your registered Google C2DM user and ask for a registration key.
package de.vogella.android.c2dm; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.EditText; import android.widget.Toast; import com.google.android.c2dm.C2DMessaging; public class RegisterActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } public void register(View view) { Log.e("Super", "Starting registration"); Toast.makeText(this, "Starting", Toast.LENGTH_LONG).show(); EditText text = (EditText) findViewById(R.id.editText1); C2DMessaging.register(this, text.getText().toString()); } }
Run your application, maintain your registered user and press the button. Check LogCat for the registration ID.
If you see " Unable to start service Intent { act=com.google.android.c2dm.intent.REGISTER pkg=com.google.android.gsf (has extras) }: not found" message make sure you are using a Google device and that you have a Google user registered on the device.
If you see " Unable to start service Intent { act=com.google.android.c2dm.intent.REGISTER pkg=com.google.android.gsf (has extras) }: not found" message 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 teh 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=This data will be send to your application as 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.
To avoid that you have to learn about server side Java programming we will "simulate" the server by another Android application. In general you could create a server side application for example with the Google App Engine .
Create a new Android application "de.vogella.android.c2dm.server" with the Activity "ServerSimulator". Assign the permission "android.permission.INTERNET" to this activity.
Change "main.xml" to the following.
Change "ServerSimulator" to the following. Make your to use your registration key, your user and your password.
To avoid that you have to learn about server side Java programming we will "simulate" the server by another Android application. In general you could create a server side application for example with the Google App Engine .
Create a new Android application "de.vogella.android.c2dm.server" with the Activity "ServerSimulator". Assign the permission "android.permission.INTERNET" to this activity.
Change "main.xml" to the following.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <Button android:text="Get Authentication" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="getAuthentification"></Button> <Button android:text="Show Authentication" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="showAuthentification"></Button> <EditText android:layout_width="match_parent" android:id="@+id/registrationId" android:hint="Registration Code" android:layout_height="wrap_content"></EditText> <Button android:text="Send Message" android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="sendMessage"></Button> </LinearLayout>
Change "ServerSimulator" to the following. Make your to use your registration key, your user and your password.
package de.vogella.android.c2dm.server; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; 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.Activity; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Bundle; import android.preference.PreferenceManager; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Toast; public class ServerSimulator extends Activity { private SharedPreferences prefManager; 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"; // Registration is currently hardcoded private final static String YOUR_REGISTRATION_STRING = "APA91bFQut1tqA-nIL1ZaV0emnp4Rb0smwCkrMHcoYRXeYVtIebJgrzOHQj0h76qKRzd3bC_JO37uJ0NgTcFO87HS9V7YC-yOP774pm0toppTHFO7Zc_PAw"; private SharedPreferences prefs; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); prefManager = PreferenceManager.getDefaultSharedPreferences(this); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.mymenu, menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menuitem_user: Intent intent = new Intent(this, UserPreferences.class); startActivity(intent); break; default: break; } return false; } public void getAuthentification(View view) { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(this); HttpClient client = new DefaultHttpClient(); HttpPost post = new HttpPost( "https://www.google.com/accounts/ClientLogin"); try { List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1); nameValuePairs.add(new BasicNameValuePair("Email", prefs.getString( "user", "undefined"))); nameValuePairs.add(new BasicNameValuePair("Passwd", prefs .getString("password", "undefined"))); nameValuePairs.add(new BasicNameValuePair("accountType", "GOOGLE")); nameValuePairs.add(new BasicNameValuePair("source", "Google-cURL-Example")); nameValuePairs.add(new BasicNameValuePair("service", "ac2dm")); 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); if (line.startsWith("Auth=")) { Editor edit = prefManager.edit(); edit.putString(AUTH, line.substring(5)); edit.commit(); String s = prefManager.getString(AUTH, "n/a"); Toast.makeText(this, s, Toast.LENGTH_LONG).show(); } } } catch (IOException e) { e.printStackTrace(); } } public void showAuthentification(View view) { String s = prefManager.getString(AUTH, "n/a"); Toast.makeText(this, s, Toast.LENGTH_LONG).show(); } public void sendMessage(View view) { try { Log.e("Tag", "Started"); String auth_key = prefManager.getString(AUTH, "n/a"); // Send a sync message to this Android device. StringBuilder postDataBuilder = new StringBuilder(); postDataBuilder.append(PARAM_REGISTRATION_ID).append("=") .append(YOUR_REGISTRATION_STRING); // if (delayWhileIdle) { // postDataBuilder.append("&").append(PARAM_DELAY_WHILE_IDLE) // .append("=1"); // } postDataBuilder.append("&").append(PARAM_COLLAPSE_KEY).append("=") .append("0"); postDataBuilder.append("&").append("data.payload").append("=") .append(URLEncoder.encode("Lars war hier", UTF8)); byte[] postData = postDataBuilder.toString().getBytes(UTF8); // Hit the dm URL. URL url = new URL("https://android.clients.google.com/c2dm/send"); HttpURLConnection conn = (HttpURLConnection) 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_key); OutputStream out = conn.getOutputStream(); out.write(postData); out.close(); int responseCode = conn.getResponseCode(); Log.e("Tag", String.valueOf(responseCode)); // Validate the response code if (responseCode == 401 || responseCode == 403) { // The token is too old - return false to retry later, will // fetch the token // from DB. This happens if the password is changed or token // expires. Either admin // is updating the token, or Update-Client-Auth was received by // another server, // and next retry will get the good one from database. Log.e("C2DM", "Unauthorized - need token"); } // Check for updated token header String updatedAuthToken = conn.getHeaderField(UPDATE_CLIENT_AUTH); if (updatedAuthToken != null && !auth_key.equals(updatedAuthToken)) { Log.i("C2DM", "Got updated auth token from datamessaging servers: " + updatedAuthToken); Editor edit = prefManager.edit(); edit.putString(AUTH, updatedAuthToken); } String responseLine = new BufferedReader(new InputStreamReader( conn.getInputStream())).readLine(); // NOTE: You *MUST* use exponential backoff if you receive a 503 // response code. // Since App Engine's task queue mechanism automatically does this // for tasks that // return non-success error codes, this is not explicitly // implemented here. // If we weren't using App Engine, we'd need to manually implement // this. if (responseLine == null || responseLine.equals("")) { Log.i("C2DM", "Got " + responseCode + " response from Google AC2DM endpoint."); throw new IOException( "Got empty response from Google AC2DM endpoint."); } String[] responseParts = responseLine.split("=", 2); if (responseParts.length != 2) { Log.e("C2DM", "Invalid message from google: " + responseCode + " " + responseLine); throw new IOException("Invalid response from Google " + responseCode + " " + responseLine); } if (responseParts[0].equals("id")) { Log.i("Tag", "Successfully sent data message to device: " + responseLine); } if (responseParts[0].equals("Error")) { String err = responseParts[1]; Log.w("C2DM", "Got error response from Google datamessaging endpoint: " + err); // No retry. throw new IOException(err); } } catch (Exception e) { throw new RuntimeException(e); } } }
No comments:
Post a Comment