Monday, December 19, 2011

android c2dm tutorial push notifications for android


This tutorial is for getting started with Android Cloud to Device Messaging (C2DM) on Android. In the iOS world it is knows as “push notifications”.
There are many good resources out there for getting started, but all of them I felt were lacking in one way or another, which is why I decided to put together this tutorial of my own. A lot of my code and structure is borrowed from other posts that are referenced in the links at the end.
Things to know before getting started:
  • There are many different ways of accomplishing the same thing (polling, constant server connections, SMS messages). But as of right now, C2DM is the best option for keeping your Android application data up to date.
  • Do not send large amounts of data in the pushes. These should be “tickles” from the server telling your Android app to go get fresh data from the server.
  • This will only work with devices running Android 2.2 and later.
  • You need to have a device or emulator that is registered with a Google account. It won’t work if the emulator is “unregistered”.  Just assign a Google account to it and you’ll be fine.
  • You will need some server side scripting to actually push messages to the device.
Step 1: Sign-up for a C2DM account with Google
Follow the steps here. Normally takes a day or so before you are ready to roll.
Step 2: Setup your Manifest
Overview of the manifest changes:
  1. Permission to receive C2DM messages
  2. Access to the internet
  3. Restrict access to your C2DM messages so no other app can see them
  4. Declare a Receiver, that we’ll create later, that will let us receive the C2DM events
  5. Make sure that the minSdkVersion is set so that only 2.2 and higher can access your app
<manifest package="com.example.myapp" ...>

   <!-- Only this application can receive the messages and registration result -->
   <permission android:name="com.example.myapp.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
   <uses-permission android:name="com.example.myapp.permission.C2D_MESSAGE" />

   <!-- This app has permission to register and receive message -->
   <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

   <!-- Send the registration id to the server -->
   <uses-permission android:name="android.permission.INTERNET" />

   <application...>
      <!-- Only C2DM servers can send messages for the app. If permission is not set -
any other app can generate it -->
      <receiver android:name=".MyC2dmReceiver"
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="com.example.myapp" />
          </intent-filter>
          <!-- Receive the registration id -->
          <intent-filter>
              <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
              <category android:name="com.example.myapp" />
          </intent-filter>
      </receiver>
      ...
   </application>
   ...
</manifest>
Quick note on this step: make sure that your receiver is declared within your application tag. I’ve seen it twice where developers declared it outside that application tag. You won’t get any errors this way, but you also won’t get and receive events. It will never get to the onReceive method. It never gets called because the receiver is declared in the wrong place. :)
Step 3: Send the Registration call
Intent registrationIntent = new Intent("com.google.android.c2dm.intent.REGISTER");

registrationIntent.putExtra("app", PendingIntent.getBroadcast(context, 0, new Intent(), 0));

registrationIntent.putExtra("sender", aliasEmailUsedAtSignup);

context.startService(registrationIntent);
Where you put this code is up to you. If just getting started, you might want to just put in your main activity. After you get that working, you probably want to put it in a Utility class like is seen in this project.
Step 4: Handling Registration in your Receiver
The registration event will come back to the receiver that we declared in our AndroidManifest.xml. Create a Receiver class that looks something like this:
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.Editor;
import android.util.Log;

import com.rain.skullcandy.R;
import com.rain.skullcandy.activities.FavoriteLocations;
import com.rain.skullcandy.model.SkullCandyModel;

public class MyC2dmReceiver extends BroadcastReceiver {
 private static String KEY = "c2dmPref";
 private static String REGISTRATION_KEY = "registrationKey";

 private Context context;
 @Override
 public void onReceive(Context context, Intent intent) {
     this.context = context;
  if (intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION")) {
         handleRegistration(context, intent);
     } else if (intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")) {
         handleMessage(context, intent);
     }
  }

 private void handleRegistration(Context context, Intent intent) {
     String registration = intent.getStringExtra("registration_id");
     if (intent.getStringExtra("error") != null) {
         // Registration failed, should try again later.
      Log.d("c2dm", "registration failed");
      String error = intent.getStringExtra("error");
      if(error == "SERVICE_NOT_AVAILABLE"){
       Log.d("c2dm", "SERVICE_NOT_AVAILABLE");
      }else if(error == "ACCOUNT_MISSING"){
       Log.d("c2dm", "ACCOUNT_MISSING");
      }else if(error == "AUTHENTICATION_FAILED"){
       Log.d("c2dm", "AUTHENTICATION_FAILED");
      }else if(error == "TOO_MANY_REGISTRATIONS"){
       Log.d("c2dm", "TOO_MANY_REGISTRATIONS");
      }else if(error == "INVALID_SENDER"){
       Log.d("c2dm", "INVALID_SENDER");
      }else if(error == "PHONE_REGISTRATION_ERROR"){
       Log.d("c2dm", "PHONE_REGISTRATION_ERROR");
      }
     } else if (intent.getStringExtra("unregistered") != null) {
         // unregistration done, new messages from the authorized sender will be rejected
      Log.d("c2dm", "unregistered");

     } else if (registration != null) {
      Log.d("c2dm", registration);
      Editor editor =
                context.getSharedPreferences(KEY, Context.MODE_PRIVATE).edit();
            editor.putString(REGISTRATION_KEY, registration);
      editor.commit();
        // Send the registration ID to the 3rd party site that is sending the messages.
        // This should be done in a separate thread.
        // When done, remember that all registration is done.
     }
 }

 private void handleMessage(Context context, Intent intent)
 {
  //Do whatever you want with the message
 }
}
A couple things to note in the above code.
  • Once you get the registration ID, you’ll want to save it to the shared preferences like I’m doing so that the ID persists. More info on shared preferences here.
  • Like the comments say, you’ll want to save the registration ID to the server as soon as you get it.
  • A good way to tell the user that something important has happened is using theNotification class. This is a great feature for the Android platform that makes iOS developers jealous :)
Step 5: Sending Messages from your server side application
The server side scripting is outside the scope of this tutorial. Some references can be found here and here
That’s it!

No comments: