Saturday 3 March 2012

Implementing Push Notifications Client for Android with C2DM


Implementing your C2DM Client :

C2DM Client(Android):

First we are going to implement our client side. It will receive the message from Google server and show a notification on the status bar Currently this code will show a notification on the status bar . In the C2DM client we need to provide the registered eMail id to get the Registration key for the client .
Go to the link and register your e-mail id with your application Package name (Do remember the Package name its very important )
http://code.google.com/android/c2dm/signup.html

You will receive a mail from C2DM giving your C2DM account status.
The most important part is the AndroidManifest.xml be vary careful while making changes .

src/

C2DMRegistration.java

public class C2DMRegistration { 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"; public static final String TAG = "REGISTRATION FOR C2DM"; // package static final String PREFERENCE = "com.google.android.c2dm"; public static long DEFAULT_BACKOFF = 3000; /** * Initiate c2d messaging registration for the current application */ public static void register(Context context ) { String mailId=context.getString(R.string.email_id); 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, mailId); 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(); } }


C2DMBaseReceiver.java
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 public static final String WAKELOCK_KEY = "C2DM_LIB"; public static PowerManager.WakeLock mWakeLock; public 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. */ public 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)) { C2DMRegistration.register(context); } } 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 = "com.android.aspark.notification.C2DMReceiver"; String receiver = context.getPackageName() + ".C2DMReceiver"; intent.setClassName(context, receiver); context.startService(intent); } public 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 C2DMRegistration.clearRegistrationId(context); onUnregistered(context); return; } else if (error != null) { // we are not registered, can try again C2DMRegistration.clearRegistrationId(context); // Registration failed Log.e(TAG, "Registration error1 " + error); onError(context, error); if ("SERVICE_NOT_AVAILABLE".equals(error)) { long backoffTimeMs = C2DMRegistration.getBackoff(context); Log.d(TAG, "Scheduling registration retry, backoff = " + backoffTimeMs); Log.i(TAG, "Registration retrying "); Intent Intent = new Intent(C2DM_RETRY); PendingIntent PIntent = PendingIntent .getBroadcast(context, 0 /* requestCode */, Intent, 0 /* flags */); // AlarmManager am = (AlarmManager) context .getSystemService(Context.ALARM_SERVICE); Log.i(TAG, "Starting Alarm "); am.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime()+backoffTimeMs, PIntent); Log.e(TAG," allerm set on "+ backoffTimeMs); // // Next retry should wait longer. backoffTimeMs*=2; C2DMRegistration.setBackoff(context, backoffTimeMs); } } else { try { onRegistered(context, registrationId); C2DMRegistration.setRegistrationId(context, registrationId); } catch (IOException ex) { Log.e(TAG, "Registration error2 " + ex.getMessage()); } } } }


C2DMBroadcastReceiver.java
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 */); } }

C2DMReceiver.java
public class C2DMReceiver extends C2DMBaseReceiver { public static String registrationID=null; public C2DMReceiver() { // Email address currently not used by the C2DM Messaging framework super("xxx"); } /* (non-Javadoc) * @see com.android.aspark.notification.C2DMBaseReceiver#onRegistered(android.content.Context, java.lang.String) */ @Override public void onRegistered(Context context, String registrationId) throws java.io.IOException { registrationID=registrationId; Log.e("C2DM", "Registration ID arrived: Fantastic!!!"); Log.e("C2DM", registrationId); //--------------------server call------------------------------------ String buildVersion = Build.VERSION.RELEASE; Log.i("device type", deviceType ); }; /* (non-Javadoc) * @see com.android.aspark.notification.C2DMBaseReceiver#onMessage(android.content.Context, android.content.Intent) */ @Override public void onMessage(Context context, Intent intent) { String action = intent.getAction(); // On Notification recived extract the Message // "Payload".............................. Log.w("C2DM", "Message Receiver called"); if ("com.google.android.c2dm.intent.RECEIVE".equals(action)) { Log.w("C2DM", "Received message"); // extracting the payload data with key value ........... final String payload = intent.getStringExtra("payload"); // expecting a unique notification id from server....................... final int notificationID=Integer.parseInt(intent.getStringExtra("notificationid")); } Log.d("C2DM", "dmControl: payload = " + payload); Log.d("C2DM", "dmControl: notificationID = " + notificationID); //Auto incrementing for testing // Note: 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,notificationID); } Log.e("C2DM", "Message Recived : Fantastic!!!"); } @Override public void onError(Context context, String errorId) { Log.e("C2DM", "Error occured!!!"); } /** * ---------------Displaying Notification on status bar --------------------- * @param context * @param payload */ public void createNotification(Context context, String payload,int notificationID) { String ns = Context.NOTIFICATION_SERVICE; NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns); int icon = R.drawable.notification_icon; CharSequence tickerText = "New Message"; long when = System.currentTimeMillis(); Notification notification = new Notification(icon, tickerText, when); CharSequence contentTitle = ""; CharSequence contentText = payload; Intent notificationIntent = new Intent(this,OnMessageReceiveActivity.class); notificationIntent.putExtra("payload", payload); notificationIntent.putExtra("notificationid", ""+notificationID); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent); mNotificationManager.notify(notificationID, notification); } }

OnMessageReceiveActivity.java
public class OnMessageReceiveActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.onmessagereceiveactivity); } }




RegisterActivity.java

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(); C2DMRegistration.register(this); } }




res/

onmessagereceiveactivity.xml

<?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"> <TextView android:id="@+id/editText1" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="You are in new activity" ></TextView> </LinearLayout>

main.xml

<?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:id="@+id/button1" android:text="Register for Push Notification" android:layout_height="wrap_content" android:onClick="register" android:layout_width="wrap_content"></Button> </LinearLayout>


AndroidManifest
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="YOUR_PACKAGE_NAME" android:versionCode="1" android:versionName="1.0" > <permission android:name="YOUR_PACKAGE_NAME.permission.C2D_MESSAGE" android:protectionLevel="signature" /> <uses-permission android:name="YOUR_PACKAGE_NAME.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.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_CORSE_LOCATION"/> <application android:icon="@drawable/ic_launcher" 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> <activity android:name=".OnMessageReceiveActivity"/> <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=".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="YOUR_PACKAGE_NAME" /> </intent-filter> <!-- Receive the registration id --> <intent-filter> <action android:name="com.google.android.c2dm.intent.REGISTRATION" /> <category android:name="YOUR_PACKAGE_NAME" /> </intent-filter> </receiver> <receiver android:name=".C2DMBroadcastReceiver" > <intent-filter> <action android:name="com.google.android.c2dm.intent.RETRY"/> <category android:name="YOUR_PACKAGE_NAME" /> </intent-filter> </receiver> </application> </manifest>

You need to declare few more things
Value ->
String :
<string name="email_id"></string> 

DRAWABLE:
1.notification_icon.png 




Now you can run your Android Client , in response you will get the auth KEY( hash value ) . Check your LogCat  .Do not change the permission and package name in manifest file.

*It will show the notifications on the status bar with a fixed message ,it will not show the message that is sent from the server side.
*for server side check my other post .C2DM Server.
*For C2DM details go to C2DM Push notification for Android
Enjoy.... 
Do post your doubts, queries or suggestions in this blog.




No comments:

Post a Comment