Saturday, May 12, 2012

Push Notification for Android (C2DM)


There are many tutorials out there for getting started push notification for android, None of them is suitable for novice programmer like me. It has take two weeks of my time to create simple working push notification demo and that's why I decided to put together this tutorial of my own. A lot of my code and structures are  borrowed from other posts. Just follow my post step by step procedures, It will work like charm :)

After android 2.2 is possible to push notification to an Android app. This service is called "Cloud to Device messaging" or short C2DM. 

Step1: Sign-up for a C2DM account with Google
Follow the steps here. While filling the form, note below parameters somewhere, which we will be used it later in the application.

Package name of your Android app * 
Role (sender) account email *


 Step 2: Create Local server to receive registration ID from mobile


I have used Apache and PHP to receive registration ID from mobile (I will brief you about how to get registration ID from mobile below)

For ease of use, you can install xampp bundle from here. It will install everything which you need for the server.  After installing xampp , copy following index.php, notify.php files into int C:\xampp\htdocs\php_push folder.

 index.php

    header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
    header("Cache-Control: no-store, no-cache, must-revalidate");
    header("Cache-Control: post-check=0, pre-check=0", false);
    header("Pragma: no-cache");
    $fname="logfiles/push.log";
    if(!file_exists("logfiles"))
    {
        mkdir("logfiles", 0755);
    }
    if (!file_exists("logfiles/push.log")) {
            $myfile = fopen ($fname, 'a');
           fclose($myfile);
    }
     $myfile = fopen ($fname, 'a');
    if($_SERVER["REQUEST_METHOD"] == "POST"){
        fputs($myfile,"Device Id is :".$_POST["deviceid"]."\n");
        fputs($myfile,"Registration ID is  :".$_POST["registrationid"]."\n");
        echo "response from php file";
    }
    fclose($myfile);
?>


 notify.php
     header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
    header("Cache-Control: no-store, no-cache, must-revalidate");
    header("Cache-Control: post-check=0, pre-check=0", false);
    header("Pragma: no-cache");
    $fname="logfiles/push.log";
    if(!file_exists("logfiles"))
    {
        mkdir("logfiles", 0755);
    }
    if (!file_exists("logfiles/push.log")) {
            $myfile = fopen ($fname, 'a');
           fclose($myfile);
    }
     $myfile = fopen ($fname, 'a');
    if($_SERVER["REQUEST_METHOD"] == "POST"){
        fputs($myfile,"notification message is :".$_POST["message"]."\n");
        echo "response from php file";
    }
    fclose($myfile);
?>


We need to wait 12 - 24 Hours to google to activate C2DM service for you.

Step 3: Get the Authentication key from the C2DM server 
You can use any programming language e.g. Java, PHP, Python, etc. or CURL tool to get the authentication key. The tricky part is , while signing up for C2DM service, google ask only to provide google gmail id ,But google knows your gmail password. To get the authentication key, we need to provide gmail id and password together.

I am using CURL tool to get the authentication token. you can also use the same. If you are using windows download CURL tool here .Extract the zip file and open the command line (Start --> Run --> cmd and the enter) navigate to the exacted folder and execute the following command.

curl https://www.google.com/accounts/ClientLogin -d Email=registered_mail_id@gmail.com -d "Passwd=mypassword" -d accountType=GOOGLE -d source=Google-cURL-Example -d service=ac2dm

From the response get the part after "Auth=" , store it in notepad or somewhere else. we need this token at the time of sending push notification to device.
for Example mine is :
DQAAAMMAAABEBszojeqMG9e8uFpzvzSv_leYyAtWNxdxh804jUnzgljrrjNeFuhB2OjZc0pc625SANcA8q1-X5Jp9wd156BbcDab1w-cXczAKITXUQTshdke0pzU4KjCDXrTzuNqPR3ogcaPQ7ICCJ-pgfzUmmx_IayjKz8QSiAyCc3bxiksyB4j617T_K0jiwzpy_5cQPg11GptA36Q3w6GouUMz10cr7mwxn1c7tV-d9zCHdeCf9ivED99GO8461i8wHCSqSltf50dmVutXevCeS94Zqb- 

* We need this ID at the time of sening push message to device. This token is periodically refreshed. 


Step 4: Requirements 


C2DM is available 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. Emulator Cant receive message from C2DM server.

Tested on Eclipse Version: 3.7.2

 Step 5:Application development

Create new android project and provide the package which you gave at the time of signing up for the C2DM service and keep the activity name C2DMClientActivity ( or you many do the necessary changes in the following source code)

* Copy and past the code in the following order

You may need to rename  ic_laucher.png to  icon.png in the  project_name/rec/drawable folder.

Copy following main.xml and activity_result.xml  files into project_name/res/layout folder

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_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>

activity_result.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" >
    <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>

Copy following C2DMClientActivity.java, C2DMMessageReceiver.java, C2DMRegistrationReceiver.java, MessageReceivedActivity.java, RegistrationResultActivity.java files into Project_name\src\your.package.com\  folder

C2DMClientActivity.java

package your.package.com;
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));
        // Use registered Google email
        intent.putExtra("sender", "Role (sender) account email");
        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);

    }
}

C2DMMessageReceiver.java
Please replace  local_machine_ip with your local machine IP where you installed XAMPP


package your.package.com;

import java.io.BufferedReader;
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.provider.SyncStateContract.Columns;
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.getExtras().getString("message");
            Log.d("C2DM", "dmControl: payload = " + payload);
            // Note: Send this to my application server to get the real data
            // Lets make something visible to show that we received the message
            sendRegistrationMessage(payload);
            createNotification(context, payload);

        }
    }
    // do this in an service and in an own thread
    public void sendRegistrationMessage(String message) {
        Log.d("C2DM", "Sending registration ID to my application server");
        HttpClient client = new DefaultHttpClient();
        HttpPost post = new HttpPost("http://local_machine_ip/php_push/notify.php");
        try {
            List nameValuePairs = new ArrayList(1);
            // Get the deviceID
            nameValuePairs.add(new BasicNameValuePair("message", message));
            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 (Exception e) {
            e.printStackTrace();
        }
    }  
  
    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;
        //adding sound to notification
        notification.defaults |= Notification.DEFAULT_SOUND;
      
        Intent intent = new Intent(context, MessageReceivedActivity.class);
        intent.putExtra("payload", payload);
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
                intent, 0);
        notification.setLatestEventInfo(context, "Message",
                "new notification", pendingIntent);
        notificationManager.notify(0, notification);

    }

}

C2DMRegistrationReceiver.java                                                                                                            
Please replace  local_machine_ip with your local machine IP where you installed XAMPP
 

package your.package.com;

import java.io.BufferedReader;
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.provider.SyncStateContract.Columns;
import android.util.Log;

public class C2DMRegistrationReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.e("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.e("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://local_machine_ip/php_push/");
        try {
            List nameValuePairs = new ArrayList(1);
            // Get the deviceID
            nameValuePairs.add(new BasicNameValuePair("deviceid", deviceId));
            nameValuePairs.add(new BasicNameValuePair("registrationid",
                    registrationId));
            Log.e(Columns.DATA, "Device Id is "+deviceId);
            Log.e(Columns.DATA, "registration id is "+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 (Exception e) {
            e.printStackTrace();
        }
    }
}

MessageReceivedActivity.java

package your.package.com;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class MessageReceivedActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setContentView(R.layout.activity_result);
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onResume() {
        // TODO Auto-generated method stub
        super.onResume();
        Bundle extras = getIntent().getExtras();
        if (extras != null) {
            Log.e("TAG", "onstart2");
            String message = extras.getString("payload");
            if (message != null && message.length() > 0) {
                TextView view = (TextView) findViewById(R.id.result);
                view.setText(message);
            }
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.e("TAG", "onstart");
        Bundle extras = getIntent().getExtras();
        if (extras != null) {
            Log.e("TAG", "onstart2");
            String message = extras.getString("payload");
            if (message != null && message.length() > 0) {
                TextView view = (TextView) findViewById(R.id.result);
                view.setText(message);
            }
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.e("TAG", "onstart2");
    }
}

RegistrationResultActivity.java

package your.package.com;

import android.app.Activity;
import android.os.Bundle;
import android.provider.SyncStateContract.Constants;
import android.util.Log;
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);
            }
        }else{
            Log.e(Constants.DATA,"Registration failed...");
        }

        super.onCreate(savedInstanceState);
    }
}

Edit AndroidManifest.xml file

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="your.package.com"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <permission
        android:name="your.package.com.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />

    <uses-permission android:name="your.package.com.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="your.package.com" />
            </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="your.package.com" />
            </intent-filter>
        </receiver>

        <activity
            android:launchMode="singleTop"
            android:name="RegistrationResultActivity" >
        </activity>
        <activity
            android:launchMode="singleTop"
            android:name="MessageReceivedActivity" >
        </activity>
    </application>

</manifest>

Step 6: Run the mobile application

Run the application on the eclips and and after installing app on the emulator , click on the register button and see the CatLog window on the clips (which is bottom of the window next to the console logs) and see the messages , you may notice that

device id is : d4dssjkd343jj3434
Registration Id is : null

then , application works fine. you cant get the registration id on the emulator.

Transfer project_name.apk from project_name\bin\ folder to android device and install it.

Turn ON WiFi and connect to the local network.

Step 7: Getting mobile registration ID

RUN the app and click on "register" button, now, you can see the notification massage saying, registration complete. then,

goto, C:\xampp\htdocs\php_push\ , now, you can see that new folder has been created called logfiles, inside that you can find push.log file. Open push.log files and you can device and registration id written

Device Id is :447ff1044f35c77d
Registration ID is  :APA91bGOwZvRecTKl1OeshYgYGujwQzrDeG2CjLiRB0qdyMx9_59Po1hCMq6cI90VtEjumXsPh8oYzalyZf06a7Ve_mx-uTOoGTwE1_o17r4tTR57GpZf0ILR2bHlVw1QnchRmhfI1o39jAZc5p1IlafqvtXRHmFrYxWtf9SY9917DjvIMRpCc8

Copy the registration ID from the push.log file and use following CURL command to send the message

Step 8: Sending Message  to mobile

curl --header "Authorization: GoogleLogin auth=DQAAAMMAAABEBszojeqMG9e8uFpzvzSv_leYyAtWNxdxh804jUnzgljrrjNeFuhB2OjZc0pc625SANcA8q1-X5Jp9wd156BbcDab1w-cXczAKITXUQTshdke0pzU4KjCDXrTzuNqPR3ogcaPQ7ICCJ-pgfzUmmx_IayjKz8QSiAyCc3bxiksyB4j617T_K0jiwzpy_5cQPg11GptA36Q3w6GouUMz10cr7mwxn1c7tV-d9zCHdeCf9ivED99GO8461i8wHCSqSltf50dmVutXevCeS94Zqb-
" "https://android.apis.google.com/c2dm/send" -d registration_id=APA91bGOwZvRecTKl1OeshYgYGujwQzrDeG2CjLiRB0qdyMx9_59Po1hCMq6cI90VtEjumXsPh8oYzalyZf06a7Ve_mx-uTOoGTwE1_o17r4tTR57GpZf0ILR2bHlVw1QnchRmhfI1o39jAZc5p1IlafqvtXRHmFrYxWtf9SY9917DjvIMRpCc8
 -d "data.message=message from third party server" -d collapse_key=0

After executing above command, you may received notifcation from C2DM server with the message "message from third party server"

Thats it, enjoy :) 






2 comments:

Unknown said...

awesome tutorial.. helped me a lot .. thanks for ur help.. :)

pushdeneme said...

could you send working project we are new for android we need please
pushdeneme@gmail.com