Android Push Notification Using GCM (client and server side implementation)

Applicable To: As specified
Updated: Wed 15 Jun, 2016 GMT

PushNotificationGcmClient
View On Github Download .zip Download .tar.gz

Android push notification example using GCM (Google Cloud Messaging) complying to the latest GCM API from Google. This includes both client side and server side implementation. Server side implementation requires PHP, a little bit CURL and MySql.

Preview

Agenda:

  1. Create a project (GCM-Demo) in Google Developer Console and enable GCM.
  2. Implement a basic but comprehensive GCM client app in Android.
  3. Implement a Push Notification Server (gcmsender) using PHP and MySqli.
  4. Test the code with a demo Push Notification Admin Panel.

Mechanism:

gcm image

Overview:

GCM (Google Cloud Messaging) documentation says:

Send data from your server to your users' devices, and receive messages from devices on the same connection. The GCM service handles all aspects of queueing of messages and delivery to client applications running on target devices, and it is completely free.

To get this service from Google, however, you will need to do certain things:

  1. Create a project on Google Developer Console
  2. Enable GCM for that project.
  3. Get the SENDER_ID of the project or create a configuration file.
  4. Implement an Android Client app to register with GCM, send the registration id to your push notification server and manage the notifications sent from your server via GCM.
  5. Implement a server side API to get and store registration ids from the client app and optionally provide an Admin panel to send push notification from.

Coverage:

This tutorial will cover the following criteria/features regarding GCM Push Notification:

Client App:

  1. Client app will register with GCM using an intent service.
  2. It will provide users the ability to retry/refresh the registration/un-registration with GCM server.
  3. It will implement registration/un-registration to your own server using an exponential back-off (a retry mechanism).
  4. It will use shared preferences to store some necessary information.
  5. By default, if a previous registration id is available, it will use that id from shared prefs instead of a fresh retrieval.
  6. The unregister button will unregister the id from both GCM server and your own notification server.
  7. The refresh button will retrieve a fresh id from GCM server and register it to your own notification server.
  8. It will be able to handle multiple types of notifications and trigger different events accordingly.

Server API:

  1. It will send notification to all subscribed users.
  2. It will show the number of registered users i.e when registering or un-registering you will be able to see the increase or decrease of the number of registered users in Admin Panel.
  3. GCM doesn't allow more than 1000 registration ids at once. It will handle this situation.
  4. It uses PHP (a little CURL) and MySqli. So make sure you have those setup before starting to write the server side code. Either WAMP, LAMP or XAMP will do, or even better if you test it from a live site. If you use localhost, then you may need to expose your localhost to the Internet to test it in a real device; for an emulator you shouldn't need to do that.
  5. I am providing a test Admin Panel with this tutorial for now. You won't be able to send any notification though (I have disabled it for security purposes); you will only be able to register/unregister and thus see the up-down of the number of registered users in Admin Panel. To take this advantage, you will need to download my source from Github and run it without changing the SERVER_URL and SENDER_ID or Google Services Configuration File (whatever is used).

Let's Start

Create a project on Google Developer Console

Create & prepare the project

  1. Go to Google Developer Console. Sign in with a Google account if not signed in already.
    Google Developer Console image
  2. Create a new project (GCM-demo).
    new project in Google Developer Console
    new project in Google Developer Console
  3. Goto Dashboard of this new project and click on Enable and manage APIs.
    Dashboard in Google Developer Console
  4. Click on Mobile APIs->Google Cloud Messaging.
    Enable and manage API in Google Developer Console
  5. Click on Enable API.
    enabling google cloud messaging in Google Developer Console
  6. Now go to the credential section for this project, click on Go to Credentials.
    enabling google cloud messaging in Google Developer Console
  7. Click on Create Credentials and then select API Key.
    creating credentials for GCM project in Google Developer Console
  8. A new widow will pop up. Select Server Key in it.
    creating server key for GCM project in Google Developer Console
  9. Give it a name, keep the IP Address section blank for now, then click on Create.
    creating server key for GCM project in Google Developer Console
  10. An API key will be generated. Copy this key and save it somewhere. We will refer to this key as API key.
    API key in Google Developer Console
  11. Go to Dashboard again. Copy and save the id of the project, the one that starts with a #. Save it somewhere without the #.
    Getting project Id in Google Developer Console

Generate a configuration file

You can skip this step if you want to use the project id that was saved from last step. But Google recommends using a configuration file instead and I will be using the configuration file method in my code, though I will give a hint on how you can use the project id instead of this configuration file.

  1. Go to this URL. If the URL is changed then go to this URL instead and find a section on Get a configuration file and follow the instructions to get to the actual link.
  2. Sign in with the previous Google Account if you are not signed in. You should see a form like this:
    GCM configuration file form
  3. Select the App name from drop-down list (GCM-demo) and put the package name of your android app, then click on the button Choose and configure services
    filling GCM configuration file form
  4. Click on Cloud Messaging and click ENABLE GOOGLE CLOUD MESSAGING.
    enabling Google Cloud Messaging in GCM configuration file
  5. Now click on Generate Configuration files.
    Generate Configuration files
  6. Click on Download google-services.json and download the file. Save it somewhere. You will need to copy this file in the app directory of your android project.
    Download google-services.json

Implement the GCM Client app

Prepare the client project:

Create an empty project in Android studio. Use whatever template you like (I have used a navigation drawer template). Copy the google-services.json file to the app directory of your project.

Edit gradle files:

  1. Add classpath 'com.google.gms:google-services:2.0.0-beta2' in the dependencies section of your project level build.gradle file.
  2. Add compile 'com.google.android.gms:play-services-gcm:8.4.0' in the dependencies section of you app level build.gradle file.
  3. Add apply plugin: 'com.google.gms.google-services' at the end of your app level build.gradle file.

This is how my Gradle files look like:

build.gradle (project level)

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.0.0-beta2'
        classpath 'com.google.gms:google-services:2.0.0-beta2'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

app/build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion '23.0.2'

    defaultConfig {
        applicationId "org.neurobin.aapps.pushnotificationgcmclient"
        minSdkVersion 9
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.android.support:design:23.1.1'
    compile 'com.google.android.gms:play-services-gcm:8.4.0'
    compile 'com.google.android.gms:play-services-ads:8.4.0'
}

apply plugin: 'com.google.gms.google-services'

gradle/wrapper/gradle-wrapper.properties

#Wed Oct 21 11:34:03 PDT 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip

Edit manifest file:

  1. Declare a permission with your package name + .permission.C2D_MESSAGE and use that permission. It prevents other Android applications from registering and receiving your app's messages
  2. Add permission for GcmReceiver. Also add android:permission="com.google.android.c2dm.permission.SEND"
  3. Add permission for GcmListenerService
  4. Add permission for InstanceIDListenerService
  5. Add permission for other activities (if any)

This is how my manifest file looks like:

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.neurobin.aapps.pushnotificationgcmclient">

    <permission
        android:name="org.neurobin.aapps.pushnotificationgcmclient.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />

    <uses-permission android:name="org.neurobin.aapps.pushnotificationgcmclient.permission.C2D_MESSAGE" />
    <!-- [START gcm_permission] -->
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <!-- [END gcm_permission] -->
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_short_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".PushNotificationMainActivity"
            android:label="@string/app_short_name"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- [START gcm_receiver] -->
        <receiver
            android:name="com.google.android.gms.gcm.GcmReceiver"
            android:exported="true"
            android:permission="com.google.android.c2dm.permission.SEND">
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                <category android:name="org.neurobin.aapps.pushnotificationgcmclient" />
            </intent-filter>
        </receiver>
        <!-- [END gcm_receiver] -->

        <!-- [START gcm_listener] -->
        <service
            android:name=".MyGcmListenerService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            </intent-filter>
        </service>
        <!-- [END gcm_listener] -->
        <!-- [START instanceId_listener] -->
        <service
            android:name=".MyInstanceIDListenerService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.android.gms.iid.InstanceID" />
            </intent-filter>
        </service>
        <!-- [END instanceId_listener] -->
        <service
            android:name=".MyGCMRegistrationIntentService"
            android:exported="false"></service>

        <activity
            android:name=".ScrollingActivity"
            android:label="@string/app_short_name"
            android:theme="@style/AppTheme.NoActionBar">

        </activity>
    </application>

</manifest>

Let's see an overview of the classes and methods used throughout this project. It will help better understand what's actually happening under the hood.

Java Classes at a glance:

  1. GCMCommonUtils: This class includes some commonly used variables and methods. You may change the SERVER_URL and other stuffs here.
  2. GCMSharedPreferences: This class holds the registration id in shared preferences and also some other values used throughout the project.
  3. MyGcmListenerService: It's an intent service which runs in the background (always) for the project and responsible for getting the notification and triggering different events accordingly.
  4. MyGCMRegistrationIntentService: It's another intent service which is responsible for registering/un-registering the device with both GCM and the notification server. It detects whether to register or un-register by evaluating the Extra value put for the intent service with putExtra() method.
  5. MyInstanceIDListenerService: An extra class which is not used in the sample project but provided for further digging. You can make use of it if you want. It can be used to handle token refresh (I am using a different but easier method to refresh tokens).
  6. PushNotificationMainActivity: This is the main activity. Includes a BroadcastReceiver to handle messages from the intent service MyGCMRegistrationIntentService, and a GCM related method startRegistrationService() which starts the MyGCMRegistrationIntentService to register/un-register the device with GCM and your server.
  7. ScrollingActivity: A dummy activity to show an example of handling multiple types of notifications.

Java methods at a glance:

  1. checkPlayServices(Activity activity): Check play services API availability and install/update if necessary. Defined in GCMCommonUtils class.
  2. onMessageReceived(String from, Bundle data): Override method in MyGcmListenerService. Handles messages received through push notification.
  3. sendNotification(String message, String type): Generates notification on the device with the message and triggers an event if clicked from the notification area in device according to the type specified.
  4. onHandleIntent(Intent intent): Override method in MyGCMRegistrationIntentService, handles registration intent, registers or unregisters with GCM and server.
  5. registerWithServer(String token, InstanceID instanceID): Defined in MyGCMRegistrationIntentService. Sends the token and the instanceID (string) to server in order to register with the server. It employs an exponential backoff mechanism to retry in case of failure.
  6. unregisterFromServer(final String regId): Defined in MyGCMRegistrationIntentService. Sends the token/regid to server and unregisters the device for the server. It employs an exponential backoff mechanism to retry in case of failure.
  7. post(String endpoint, Map<String, String> params): Defined in MyGCMRegistrationIntentService. It triggers an HTTP post request.
  8. startRegistrationService(boolean reg, boolean tkr): Defined in PushNotificationMainActivity. If reg is true, it tries to register, if false it tries to un-register. If tkr is true, a fresh token is retrieved, otherwise old token from shared preferences is used.

Java codes:

GCMCommonUtils.java

package org.neurobin.aapps.pushnotificationgcmclient;

import android.app.Activity;
import android.util.Log;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;

/**
 * Created by jahid on 16/02/16.
 */
public class GCMCommonUtils {

    private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
    // put your server registration url here, must end with a /
    public static final String SERVER_URL = "https://neurobin.org/api/android/gcm/gcmsender/";

    public static String notificationType[] = {"default", "type1"};

    /**
     * Tag used on log messages.
     */
    static final String TAG = "androidpushnotification";

    public static final String DISPLAY_MESSAGE_ACTION =
            "org.neurobin.aapps.pushnotificationclient";

    static final String EXTRA_MESSAGE = "message";

    /**
     * Check the device to make sure it has the Google Play Services APK. If
     * it doesn't, display a dialog that allows users to download the APK from
     * the Google Play Store or enable it in the device's system settings.
     */
    public static boolean checkPlayServices(Activity activity) {
        GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
        int resultCode = apiAvailability.isGooglePlayServicesAvailable(activity);
        if (resultCode != ConnectionResult.SUCCESS) {
            if (apiAvailability.isUserResolvableError(resultCode)) {
                apiAvailability.getErrorDialog(activity, resultCode, PLAY_SERVICES_RESOLUTION_REQUEST)
                        .show();
            } else {
                Log.i(TAG, "This device is not supported.");
                activity.finish();
            }
            return false;
        }
        return true;
    }

}

Change the SERVER_URL to your actual server url. Be sure to end the SERVER_URL with a /. Note the notificationType array. In general case, you will only be using the default type. You can change these types or add more, but in that case you will need to change the server side code (index.php) to match these types and also in sendNotification() method inside MyGCMListenerService class.


GCMSharedPreferences.java

package org.neurobin.aapps.pushnotificationgcmclient;

/**
 * Created by jahid on 16/02/16.
 */
public class GCMSharedPreferences {

    public static final String SENT_TOKEN_TO_SERVER = "SENT_TOKEN_TO_SERVER";
    public static final String REG_SUCCESS = "REG_SUCCESS";
    public static final String SENT_UNREG_REQUEST_TO_SERVER = "SENT_UNREG_REQUEST_TO_SERVER";
    public static final String GOT_TOKEN_FROM_GCM = "GOT_TOKEN_FROM_GCM";
    public static final String REGISTRATION_COMPLETE = "REGISTRATION_COMPLETE";
    public static final String REG_ID = "";

}

MyGcmListenerService.java

package org.neurobin.aapps.pushnotificationgcmclient;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import com.google.android.gms.gcm.GcmListenerService;

/**
 * Created by jahid on 16/02/16.
 */
public class MyGcmListenerService extends GcmListenerService {

    private static final String TAG = "MyGcmListenerService";

    /**
     * Called when message is received.
     *
     * @param from SenderID of the sender.
     * @param data Data bundle containing message data as key/value pairs.
     *             For Set of keys use data.keySet().
     */
    // [START receive_message]
    @Override
    public void onMessageReceived(String from, Bundle data) {
        String message = "";
        String type = "";
        Log.d("data: ", data.toString());
        for (String not_type : GCMCommonUtils.notificationType) {
            if (data.get(not_type) != null) {
                message = data.getString(not_type);
                type = not_type;

            }
        }
        Log.d(TAG, "From: " + from);
        Log.d(TAG, "Message: " + message);

        sendNotification(message, type);
        // [END_EXCLUDE]
    }
    // [END receive_message]

    /**
     * Create and show a simple notification containing the received GCM message.
     *
     * @param message GCM message received.
     */
    private void sendNotification(String message, String type) {
        Intent intent = new Intent(this, PushNotificationMainActivity.class);
        if (type.equalsIgnoreCase("default")) {
            //nothing will be changed.
        } else if (type.equalsIgnoreCase("type1")) {
            //define intent to do some other tasks than running the main activity
            intent = new Intent(this,ScrollingActivity.class);
        } else if (type.equalsIgnoreCase("type2")) {
            //define intent to do some other tasks than running the main activity
        }
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
                PendingIntent.FLAG_ONE_SHOT);

        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle(getString(R.string.app_short_name))
                .setContentText(message)
                .setAutoCancel(true)
                .setSound(defaultSoundUri)
                .setContentIntent(pendingIntent);
        //notificationBuilder.setDefaults(Notification.DEFAULT_VIBRATE);

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
    }
}

See the if...else block inside sendNotification() method on how you can handle multiple types of notifications and trigger different events for different types of notifications. Note that notification type does not refer to GCM topics , they are entirely two different things.


MyGCMRegistrationIntentService.java

package org.neurobin.aapps.pushnotificationgcmclient;

import android.app.IntentService;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.widget.Toast;

import com.google.android.gms.gcm.GoogleCloudMessaging;
import com.google.android.gms.iid.InstanceID;

import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;

/**
 * Created by jahid on 16/02/16.
 */
public class MyGCMRegistrationIntentService extends IntentService {

    private static final String TAG = "RegIntentService";
    private static final String[] TOPICS = {"global"};
    private static final int MAX_ATTEMPTS = 10;
    private static final int BACKOFF_MILLI_SECONDS = 500;
    private static final Random random = new Random();
    SharedPreferences sharedPreferences;

    public MyGCMRegistrationIntentService() {
        super(TAG);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        Intent registrationComplete = new Intent(GCMSharedPreferences.REGISTRATION_COMPLETE);
        registrationComplete.putExtra("prefix", "");
        String token = sharedPreferences.getString(GCMSharedPreferences.REG_ID, "");

        try {
            // [START register_for_gcm]
            // Initially this call goes out to the network to retrieve the token, subsequent calls
            // are local.
            // R.string.gcm_defaultSenderId (the Sender ID) is typically derived from google-services.json.
            // See https://developers.google.com/cloud-messaging/android/start for details on this file.
            // [START get_token]
            InstanceID instanceID = InstanceID.getInstance(this);

            if (!intent.getExtras().getBoolean("register")) {
                //client wants to un-register

                //instanceID.deleteInstanceID();  //This will delete the id i.e full un-register
                instanceID.deleteToken(getString(R.string.gcm_defaultSenderId),
                        GoogleCloudMessaging.INSTANCE_ID_SCOPE); //This will delete the token not the ID (partial)
                //delete from shared preferences
                //You would generally delete selectively
                //As this project is only about GCM notification, we can clear the sharedpreferences altogether
                sharedPreferences.edit().clear().apply();
                //un-register from server
                unregisterFromServer(token);

            } else {
                if (!intent.getExtras().getBoolean("tokenRefreshed") && !token.equals("")) {
                    //No need of retrieving token from GCM
                    registrationComplete.putExtra("prefix", "Old Token:\n");
                } else {
                    //retrieve a fresh token from GCM, it may or may not be the same as previous
                    token = instanceID.getToken(getString(R.string.gcm_defaultSenderId),
                            GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
                    registrationComplete.putExtra("prefix", "Fresh Token:\n");
                }
                // [END get_token]
                Log.i(TAG, "GCM Registration Token: " + token);

                sharedPreferences.edit().putString(GCMSharedPreferences.REG_ID, token).apply();
                sharedPreferences.edit().putBoolean(GCMSharedPreferences.REG_SUCCESS, true).apply();

                // send registration request to server
                registerWithServer(token, instanceID);

                // Subscribe to topic channels
                //subscribeTopics(token);
                // [END register_for_gcm]
            }
        } catch (Exception e) {
            sharedPreferences.edit().putBoolean(GCMSharedPreferences.REG_SUCCESS, false).apply();
            Log.d(TAG, "Registration service failed.", e);
        }
        // Notify UI that task has completed, so the progress indicator can be hidden.
        if (intent.getExtras().getBoolean("register"))
            registrationComplete.putExtra("register", true);
        else
            registrationComplete.putExtra("register", false);
        LocalBroadcastManager.getInstance(this).sendBroadcast(registrationComplete);
    }

    /**
     * Persist registration to third-party servers.
     * <p/>
     * Modify this method to associate the user's GCM registration token with any server-side account
     * maintained by your application.
     *
     * @param token The new token.
     */
    private void registerWithServer(String token, InstanceID instanceID) {

//        Log.i(TAG, "registering device (regId = " + regId + ")");
        String serverUrl = GCMCommonUtils.SERVER_URL + "register.php";
        Map<String, String> params = new HashMap<String, String>();
        params.put("regId", token);
        params.put("instanceId",instanceID.toString());
//        params.put("name", name);
//        params.put("email", email);

        long backoff = BACKOFF_MILLI_SECONDS + random.nextInt(1000);
        // Once GCM returns a registration id, we need to register on our server
        // As the server might be down, we will retry it a couple of
        // times.
        for (int i = 1; i <= MAX_ATTEMPTS; i++) {
//            Log.d(TAG, "Attempt #" + i + " to register");
            try {
                post(serverUrl, params);

                // You should store a boolean that indicates whether the generated token has been
                // sent to your server. If the boolean is false, send the token to your server,
                // otherwise your server should have already received the token.
                sharedPreferences.edit().putBoolean(GCMSharedPreferences.SENT_TOKEN_TO_SERVER, true).apply();
                return;
            } catch (IOException e) {
                Toast.makeText(this, "Failed to register...", Toast.LENGTH_SHORT).show();
                sharedPreferences.edit().putBoolean(GCMSharedPreferences.SENT_TOKEN_TO_SERVER, false).apply();
                // Here we are simplifying and retrying on any error; in a real
                // application, it should retry only on unrecoverable errors
                // (like HTTP error code 503).
//                Log.e(TAG, "Failed to register on attempt " + i + ":" + e);
                if (i == MAX_ATTEMPTS) {
                    break;
                }
                try {
//                    Log.d(TAG, "Sleeping for " + backoff + " ms before retry");
                    Thread.sleep(backoff);
                } catch (InterruptedException e1) {
                    // Activity finished before we complete - exit.
//                    Log.d(TAG, "Thread interrupted: abort remaining retries!");
                    Thread.currentThread().interrupt();
                    return;
                }
                // increase backoff exponentially
                backoff *= 2;
            }
        }
    }

    /**
     * Unregister this account/device pair within the server.
     */
    private void unregisterFromServer(final String regId) {
        // Log.i(TAG, "registering device (regId = " + regId + ")");
        String serverUrl = GCMCommonUtils.SERVER_URL + "unregister.php";
        Map<String, String> params = new HashMap<String, String>();
        params.put("regId", regId);

        long backoff = BACKOFF_MILLI_SECONDS + random.nextInt(1000);
        // Once GCM returns a registration id, we need to unregister on our server
        // As the server might be down, we will retry it a couple
        // times.
        for (int i = 1; i <= MAX_ATTEMPTS; i++) {
            try {
                post(serverUrl, params);
                sharedPreferences.edit().putBoolean(GCMSharedPreferences.SENT_TOKEN_TO_SERVER, true).apply();
                return;
            } catch (IOException e) {
                Toast.makeText(this, "Failed to un-register...", Toast.LENGTH_SHORT).show();
                sharedPreferences.edit().putBoolean(GCMSharedPreferences.SENT_TOKEN_TO_SERVER, false).apply();
                // Here we are simplifying and retrying on any error; in a real
                // application, it should retry only on unrecoverable errors
                // (like HTTP error code 503).
//                Log.e(TAG, "Failed to register on attempt " + i + ":" + e);
                if (i == MAX_ATTEMPTS) {
                    break;
                }
                try {
//                    Log.d(TAG, "Sleeping for " + backoff + " ms before retry");
                    Thread.sleep(backoff);
                } catch (InterruptedException e1) {
                    // Activity finished before we complete - exit.
//                    Log.d(TAG, "Thread interrupted: abort remaining retries!");
                    Thread.currentThread().interrupt();
                    return;
                }
                // increase backoff exponentially
                backoff *= 2;
            }
        }
//        String message = context.getString(R.string.server_register_error,
//                MAX_ATTEMPTS);
//        CommonUtilities.displayMessage(context, message);
    }

    /**
     * Issue a POST request to the server.
     *
     * @param endpoint POST address.
     * @param params   request parameters.
     * @throws IOException propagated from POST.
     */
    private static void post(String endpoint, Map<String, String> params)
            throws IOException {

        URL url;
        try {
            url = new URL(endpoint);
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("invalid url: " + endpoint);
        }
        StringBuilder bodyBuilder = new StringBuilder();
        Iterator<Map.Entry<String, String>> iterator = params.entrySet().iterator();
        // constructs the POST body using the parameters
        while (iterator.hasNext()) {
            Map.Entry<String, String> param = iterator.next();
            bodyBuilder.append(param.getKey()).append('=')
                    .append(param.getValue());
            if (iterator.hasNext()) {
                bodyBuilder.append('&');
            }
        }
        String body = bodyBuilder.toString();
//        Log.v(TAG, "Posting '" + body + "' to " + url);
        byte[] bytes = body.getBytes();
        HttpURLConnection conn = null;
        try {
            Log.e("URL", "> " + url);
            conn = (HttpURLConnection) url.openConnection();
            conn.setDoOutput(true);
            conn.setUseCaches(false);
            conn.setFixedLengthStreamingMode(bytes.length);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type",
                    "application/x-www-form-urlencoded;charset=UTF-8");
            // post the request
            OutputStream out = conn.getOutputStream();
            out.write(bytes);
            out.close();
            // handle the response
            int status = conn.getResponseCode();
            if (status != 200) {
                throw new IOException("Post failed with error code " + status);
            }
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
    }

    /**
     * Subscribe to any GCM topics of interest, as defined by the TOPICS constant.
     *
     * @param token GCM token
     * @throws IOException if unable to reach the GCM PubSub service
     */
    // [START subscribe_topics]
    /*private void subscribeTopics(String token) throws IOException {
        GcmPubSub pubSub = GcmPubSub.getInstance(this);
        for (String topic : TOPICS) {
            pubSub.subscribe(token, "/topics/" + topic, null);
        }
    }*/
    // [END subscribe_topics]

}

Notice the part token = instanceID.getToken(getString(R.string.gcm_defaultSenderId) . The R.string.gcm_defaultSenderId resource is automatically parsed from the google-services.json file. If you didn't include the configuration file then put the sender id i.e the project id that you saved from Google developer console.


MyInstanceIDListenerService.java

package org.neurobin.aapps.pushnotificationgcmclient;

import android.content.Intent;

import com.google.android.gms.iid.InstanceIDListenerService;

/**
 * Created by jahid on 16/02/16.
 */
public class MyInstanceIDListenerService extends InstanceIDListenerService {

        private static final String TAG = "MyInstanceIDLS";

        /**
         * Called if InstanceID token is updated. This may occur if the security of
         * the previous token had been compromised. This call is initiated by the
         * InstanceID provider.
         */
        // [START refresh_token]
        @Override
        public void onTokenRefresh() {
            // Fetch updated Instance ID token and notify our app's server of any changes (if applicable).
            Intent intent = new Intent(this, MyGCMRegistrationIntentService.class);
            intent.putExtra("register",true); //register=true
            intent.putExtra("tokenRefreshed",true); //tokenRefreshed = true
            startService(intent);
        }
        // [END refresh_token]

}

PushNotificationMainActivity.java

package org.neurobin.aapps.pushnotificationgcmclient;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.design.widget.NavigationView;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

public class PushNotificationMainActivity extends AppCompatActivity
        implements NavigationView.OnNavigationItemSelectedListener, View.OnClickListener {

    private static final String TAG = "MainActivity";

    private BroadcastReceiver mRegistrationBroadcastReceiver;
    private ProgressBar mRegistrationProgressBar;
    private TextView mInformationTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gcm_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.setDrawerListener(toggle);
        toggle.syncState();

        NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(this);

        Button rtbtn = (Button) findViewById(R.id.ret_btn);
        Button unreg_btn = (Button) findViewById(R.id.unreg_btn);
        rtbtn.setOnClickListener(this);
        unreg_btn.setOnClickListener(this);

        //GCM related part

        mRegistrationProgressBar = (ProgressBar) findViewById(R.id.registrationProgressBar);
        mRegistrationBroadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                mRegistrationProgressBar.setVisibility(ProgressBar.GONE);
                SharedPreferences sharedPreferences =
                        PreferenceManager.getDefaultSharedPreferences(context);
                boolean sentToken = sharedPreferences
                        .getBoolean(GCMSharedPreferences.SENT_TOKEN_TO_SERVER, false);
                String tkmsg = "\nRegistration ID from GCM: " +
                        sharedPreferences.getString(GCMSharedPreferences.REG_ID, "N/A");
                tkmsg = getString(R.string.gcm_send_message) + tkmsg;
                if (!intent.getExtras().getBoolean("register"))
                    tkmsg = getString(R.string.gcm_unregister_message);
                tkmsg = intent.getStringExtra("prefix") + tkmsg;
                if (sentToken) {
                    mInformationTextView.setText(tkmsg);
                } else {
                    mInformationTextView.setText(getString(R.string.token_error_message) + tkmsg);
                }
            }
        };
        mInformationTextView = (TextView) findViewById(R.id.informationTextView);

        startRegistrationService(true, false);
        //GCM related part end

    }

    @Override
    protected void onResume() {
        super.onResume();
        LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver,
                new IntentFilter(GCMSharedPreferences.REGISTRATION_COMPLETE));
    }

    @Override
    protected void onPause() {
        LocalBroadcastManager.getInstance(this).unregisterReceiver(mRegistrationBroadcastReceiver);
        super.onPause();
    }

    @Override
    public void onBackPressed() {
        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.gcm_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @SuppressWarnings("StatementWithEmptyBody")
    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        // Handle navigation view item clicks here.
        int id = item.getItemId();

        if (id == R.id.nav_home) {
            // Handle the camera action
        } else if (id == R.id.nav_gallery) {
            Intent intent = new Intent(this, ScrollingActivity.class);
            startActivity(intent);

        } else if (id == R.id.nav_slideshow) {

        } else if (id == R.id.nav_manage) {

        } else if (id == R.id.nav_share) {

        } else if (id == R.id.nav_send) {

        }

        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        drawer.closeDrawer(GravityCompat.START);
        return true;
    }

    @Override
    public void onClick(View view) {
        if (view.getId() == R.id.ret_btn) {
            startRegistrationService(true, true);
        }
        if (view.getId() == R.id.unreg_btn) {
            //try to unregister
            startRegistrationService(false, false);
        }

    }

    public void startRegistrationService(boolean reg, boolean tkr) {

        if (GCMCommonUtils.checkPlayServices(this)) {
            mRegistrationProgressBar = (ProgressBar) findViewById(R.id.registrationProgressBar);
            mRegistrationProgressBar.setVisibility(View.VISIBLE);
            TextView tv = (TextView) findViewById(R.id.informationTextView);
            if (reg) tv.setText(R.string.registering_message);
            else tv.setText(R.string.unregistering_message);
            Toast.makeText(this, "Background service started...", Toast.LENGTH_LONG).show();
            // Start IntentService to register this application with GCM.
            Intent intent = new Intent(this, MyGCMRegistrationIntentService.class);
            intent.putExtra("register", reg);
            intent.putExtra("tokenRefreshed", tkr);
            startService(intent);
        }

    }

}

This is the main activity. It may differ in your case depending on how you have set up the project from the beginning. Use whatever activity and xml resources you like but be sure to include a TextView a ProgressBar and two Buttons if you don't want to change the codes for those resources.

The only GCM related codes in this activity are the codes that are marked with //GCM related part .. //GCM related part end and the startRegistrationService() method. Also note that the activity implements the View.OnClickListener class to handle onclick event on the buttons and the onClick() override method calls the GCM related method startRegistrationService() on demand.

ScrollingActivity.java

package org.neurobin.aapps.pushnotificationgcmclient;

import android.os.Bundle;
import android.app.Activity;

public class ScrollingActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scrolling);
    }
}

Notable XML codes:

XML codes are dependent on how you have setup the project from the beginning. I have used a navigation drawer template on this project and thus there are lots of XML codes involved, but you may not need all those. I am including some vital parts (that I have used) of the project:

The TextView, ProgressBar and Buttons:
    <TextView android:text="@string/registering_message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/informationTextView"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/registrationProgressBar" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="REFRESH"
        android:id="@+id/ret_btn"
        android:background="#4ec9ac"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:enabled="true"
        android:clickable="true"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp"
        android:textStyle="bold" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="UNREG"
        android:id="@+id/unreg_btn"
        android:layout_gravity="center_horizontal"
        android:gravity="center"
        android:background="#B90000"
        android:alpha=".9"
        android:clickable="true"
        android:enabled="true"
        android:hint="Unsubscribe"
        android:textColor="#fff"
        android:textStyle="bold"
        android:layout_marginBottom="5dp"
        android:layout_marginTop="5dp" />

String resources:

    <string name="app_name">Push Notification GCM Client</string>
    <string name="app_short_name">PNGCM Demo</string>

    <string name="navigation_drawer_open">Open navigation drawer</string>
    <string name="navigation_drawer_close">Close navigation drawer</string>

    <string name="action_settings">Settings</string>
    <string name="server_registered">Successfully registered on server</string>
    <string name="gcm_send_message">Token retrieved and sent to server! You can now use gcmsender to
        send downstream messages to this app.</string>
    <string name="gcm_unregister_message">Token unsubscribed from GCM and notification server. You will no longer
    get any notification.</string>
    <string name="registering_message">Generating InstanceID token...</string>
    <string name="unregistering_message">Un-registering Notification Service...</string>
    <string name="token_error_message">An error occurred while either fetching the InstanceID token,
        sending the fetched token to the server. Please refresh.</string>

If you are still having problems with XML and other resources, feel free to download the project from Github. Links at top.

That's it. You should have now successfully created the client app. if you are having problems with configuring or resource dependencies, then download my project from Github. Also you don't need to change anything at all in the downloaded project and don't need to have a server to run the code. The example code uses my test server to register or unregister. You can view my test admin panel here, I am not allowing sending push notification though.

Implement a gcmsender with PHP and MySqli

Now, let's do some server side coding. To test this you need an working server. Either WAMP, XAMP, LAMP or a Live server running PHP will do. Make sure that the server supports php curl module and MySqli.

If you want to run it in a localhost, then you will need to use the client app only in emulator; if you want to test the client app with real device, you may need to connect your device with the same connection as your localhost or expose your localhost to Internet so that your device can send post requests to localhost.

For example, If your device and localhost are in the same wifi, there's nothing to worry about.

My recommendation is to test it in a live server with a real device.

Prepare the database

You can easily create a database with phpmyadmin. If you have set up your server correctly and installed phpmyadmin, then in localhost the url http://localhost/phpmyadmin should open it for you. How to set up the server or install phpmyadmin is out of the scope of this tutorial. If you don't know how to do that, you will need to search a bit and learn that first before you go any further, as I don't have any related tutorial on my site yet.

If you are on a live server you can open phpmyadmin from cpanel or whatever control panel you have.

If there are other means to create MySql database, database user, and running SQL command, then that will do too.

  1. Create a database. Give it a name gcm (for now). (In cpanel you may have a separate option for this, and on localhost phpmyadmin will let you do it).
  2. Create a database user. (In cpanel you may have a separate option for this, and on localhost phpmyadmin will let you do it).
  3. Run the following SQL command on that database:
    CREATE TABLE IF NOT EXISTS `gcm_users` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `gcm_instance_id` text,
      `gcm_regid` text,
      `name` varchar(50) NOT NULL,
      `email` varchar(255) NOT NULL,
      `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
    
    It will create a table named gcm_users with 6 columns.

Coding the gcmsender with PHP

PHP files at a glance:

  1. commonutils.php: Common utilities.
  2. config.php: Defines constants. Among all the codes, only this file needs to be modified. Other files can be left untouched.
  3. db_functions.php: Database functions for making database connection, storing/retrieving users and their info and performing various tests on the gcm_users table.
  4. gcm_main.php: This is the file that is responsible for sending push notification.
  5. index.php: This is the form i.e the admin panel page.
  6. register.php: This is the php file that your client app should post registration id to register with your server.
  7. unregister.php: This is the php file that your client app should post registration id to un-register with your server.

Notable PHP functions at a glance:

  1. redirect($url): Redirects to the specified url.
  2. storeUser($gcm_regid, $instanceId, $name, $email): Member function of DB_Functions_GCM class. Stores registration id in database along with other info.
  3. checkUserById($id): Member function of DB_Functions_GCM class. Checks if an existing user already exists in database with the same registration id.
  4. deleteUserById($id): Member function of DB_Functions_GCM class. Deletes a user from database according to the registration id (unsubscribe).
  5. sendPushNotification($registration_ids, $message): Sends push notification $message to all ids in the $registration_ids array.

PHP codes:

config.php

<?php
/**
 * Database config variables
 */
define("DB_HOST", "localhost");
define("DB_USER", "");          //fill with value
define("DB_PASSWORD", "");      //fill with value
define("DB_DATABASE", "");      //fill with value

/*
 * Google API Key
 */
define("GOOGLE_API_KEY", ""); // Place your Google API Key
//define('GOOGLE_API_URL','https://android.googleapis.com/gcm/send'); //deprecated
define("GOOGLE_API_URL","https://gcm-http.googleapis.com/gcm/send");

define("BASE_URL", "https://neurobin.org/"); //optional
define("PWD", "./");
define("PWP", "https://neurobin.org/api/android/gcm/gcm-server-demo/"); //optional

?>

Edit this file according to how you have set up things so far. DB_DATABASE is the name of the database (gcm). You don't need to change the DB_HOST, GOOGLE_API_URL and PWD part.


commonutils.php

<?php
function redirect($url) {
    if (!headers_sent()) {
        header('Location: ' . $url);
        exit ;
    } else {
        echo '<script type="text/javascript">';
        echo 'window.location.href="' . $url . '";';
        echo '</script>';
        echo '<noscript>';
        echo '<meta http-equiv="refresh" content="0;url=' . $url . '" />';
        echo '</noscript>';
        exit ;
    }
}

?>

You can use this file as it is. No modifications needed.


db_functions.php

<?php
require_once 'config.php';

class DB_Functions_GCM {

    //put your code here
    // constructor
    function __construct() {
        // connecting to database
        $GLOBALS['mysqli_connection'] = mysqli_connect(DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE) or die("Mysqli Error " . mysqli_error($GLOBALS['mysqli_connection']));

    }

    // destructor
    function __destruct() {

    }

    public function connectDefaultDatabase() {

        $GLOBALS['mysqli_connection'] = mysqli_connect(DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE) or die("Mysqli Error " . mysqli_error($GLOBALS['mysqli_connection']));
        return $GLOBALS['mysqli_connection'];
    }

    public function selectDatabase($db) {
        mysqli_select_db($GLOBALS['mysqli_connection'], $db);
    }

    public function closeDatabase() {
        mysqli_close($GLOBALS['mysqli_connection']);
    }

    public function connectNewDatabase($host, $user, $password, $dbname = "") {
        closeDatabase();

        if ($dbname != "" && $dbname != null) {
            $GLOBALS['mysqli_connection'] = mysqli_connect($host, $user, $password, $dbname) or die("Mysqli Error " . mysqli_error($GLOBALS['mysqli_connection']));
        } else {$GLOBALS['mysqli_connection'] = mysqli_connect($host, $user, $password) or die("Mysqli Error " . mysqli_error($GLOBALS['mysqli_connection']));
        }

        return $GLOBALS['mysqli_connection'];
    }

    /**
     * Storing new user
     * returns user details
     */

    /**
     * Get user by email and password
     */
    public function getUserByEmail($email) {
        $result = mysqli_query($GLOBALS['mysqli_connection'], "SELECT * FROM gcm_users WHERE email = '$email' LIMIT 1");
        return $result;
    }

    public function getUserByName($username) {
        $result = mysqli_query($GLOBALS['mysqli_connection'], "SELECT * FROM gcm_users WHERE name = '$username' LIMIT 1");
        return $result;
    }

    public function getUserById($id) {
        $result = mysqli_query($GLOBALS['mysqli_connection'], "SELECT * FROM gcm_users WHERE id = '$id' LIMIT 1");
        return $result;
    }

    /**
     * Getting all users
     */
    public function getAllUsers() {
        $result = mysqli_query($GLOBALS['mysqli_connection'], "select * FROM gcm_users");
        return $result;
    }

    /**
     * Check user exists or not
     */
    public function checkUserById($id) {
        $result = mysqli_query($GLOBALS['mysqli_connection'], "SELECT gcm_regid from gcm_users WHERE gcm_regid = '$id'");
        $no_of_rows = mysqli_num_rows($result);
        if ($no_of_rows > 0) {
            return true;
        } else {
            return false;
        }
    }

    public function deleteUserById($id) {
        $result = mysqli_query($GLOBALS['mysqli_connection'], "DELETE FROM gcm_users WHERE gcm_regid = '$id'");
        if ($result) {
            return true;
        } else {
            return false;
        }

    }

    public function storeUser($gcm_regid, $instanceId, $name, $email) {
    echo "$gcm_regid";
        // insert user into database
        $result = mysqli_query($GLOBALS['mysqli_connection'], "INSERT INTO gcm_users(name, email, gcm_instance_id, gcm_regid, created_at) VALUES('$name', '$email', '$instanceId', '$gcm_regid', NOW())");

        // check for successful store
        if ($result) {
            // get user details
            $id = mysqli_insert_id($GLOBALS['mysqli_connection']);
            // last inserted id
            $result = mysqli_query($GLOBALS['mysqli_connection'], "SELECT * FROM gcm_users WHERE id = $id") or die("Error " . mysqli_error($GLOBALS['mysqli_connection']));
            // return user details
            if (mysqli_num_rows($result) > 0) {
                return mysqli_fetch_array($result);
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

}
?>

You can use this file as it is. No modifications needed.


gcm_main.php


<?php

require_once 'db_functions.php';
$db = new DB_Functions_GCM();
require_once 'commonutils.php';

//////////////Do some validation First//////////////
$error = 0;
if (isset($_POST['message'])) {
    $pushMessage = $_POST['message'];
    if ($pushMessage == "" || $pushMessage == null) {echo "<br>Error: message can not be empty<br>";
        $error++;
    }
} else {echo "<br>Are you somehow trying to hack me?<br>Forget it buddy...<br>Close your computer and go to sleep...<br>";
    $error++;
}

$nottype = (isset($_REQUEST['dropdown']) ? $_REQUEST['dropdown'] : "select");
if ($nottype == "select") {echo "<br>Error: Notification type must be selected.<br>";
    $error++;
}

//////exit if error/////
if ($error != 0) {echo "<br><a href=\"" . constant("PWD") . "\">Go Back</a><br>";
    exit ;
}

function sendPushNotification($registration_ids, $message) {

    $url = GOOGLE_API_URL;

    $fields = array('registration_ids' => $registration_ids, 'data' => $message, );

    $headers = array('Authorization:key=' . GOOGLE_API_KEY, 'Content-Type: application/json');
    echo json_encode($fields);
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));

    $result = curl_exec($ch);
    if ($result === false)
        die('Curl failed ' . curl_error());

    curl_close($ch);
    return $result;

}

$pushStatus = '';

$query = "SELECT gcm_regid FROM gcm_users";
if ($query_run = mysqli_query($GLOBALS['mysqli_connection'], $query)) {

    $gcmRegIds = array();
    while ($query_row = mysqli_fetch_assoc($query_run)) {

        array_push($gcmRegIds, $query_row['gcm_regid']);

    }

}

if (isset($gcmRegIds) && isset($pushMessage)) {
    $pushMessage = html_entity_decode($pushMessage);
    $message = array($nottype => $pushMessage);
    $regIdChunk = array_chunk($gcmRegIds, 1000);
    foreach ($regIdChunk as $RegId) {
        $pushStatus = sendPushNotification($RegId, $message);
    }
    redirect(PWD); //Comment this if you do not want to be redirected to previous page
} else {
    echo "Unknown error occured, contact your Push Notification Service Provider";

    exit ;
}
?>

You can use this file as it is. No modifications needed.


index.php

<html>
    <head>
        <title>Push Notification Admin Panel</title>
        <style type="text/css">
            html {
                height: 100%;
                font-size: 62.5%
            }

            body {
                height: 100%;
                background-color: #FFFFFF;
                font: 1.4em Verdana, Arial, Helvetica, sans-serif;
            }

            /* ==================== Form style sheet ==================== */

            /*div { align-content: center; padding-bottom: 30px; }*/

            fieldset {
                display: inline;
                align-content: center;
                border: 1px solid #095D92;
                width: 50%
            }
            legend {
                font-size: 1.1em;
                background-color: #707070;
                color: #FFFFFF;
                font-weight: bold;
            }

            input.submit-button {
                font: 1.4em Georgia, "Times New Roman", Times, serif;
                letter-spacing: 1px;
                display: block;
                width: 25em;
                height: 2em;
                background-color: #d57079;
            }

            /* ==================== Form style sheet END ==================== */

</style>
    </head>
    <body>

        <?php
        require_once 'db_functions.php';
        $db = new DB_Functions_GCM();
        $users = $db -> getAllUsers();
        if ($users != false)
            $no_of_users = mysqli_num_rows($users);
        else
            $no_of_users = 0;
        ?>

        <div><fieldset>
    <h1>Neurobin Example</h1>
    <h3>Push Notification Admin Panel</h3></fieldset></div><br></br>
     <div><fieldset><legend>Current Registered users</legend>
    <?php echo $no_of_users; ?></fieldset></div><br></br>
    <form method = 'POST' action = 'gcm_main.php' enctype="multipart/form-data">
    <div><fieldset><legend>Notification Type</legend>

<select name="dropdown">
  <option value="default" selected="selected">Default</option>
  <option value="type1">Type1</option>
</select></fieldset>
</div><br></br>
<div><fieldset><legend>Notification Message</legend>

        <div>

            <textarea rows = "3" name = "message" cols = "75" placeholder = "Type message here (keep as short as possible)" required></textarea>
        </div></fieldset></div><br>
        <div>
            <input type = "submit" value = "Send Notification">
        </div>

    </form>
    </body>
</html>

You can use this file as it is. No modifications needed. But you may want to modify it according to your choice and add as many eye candies as you want and make your admin panel look waaay more cool.

Note the part
<select name="dropdown">
  <option value="default" selected="selected">Default</option>
  <option value="type1">Type1</option>
</select>
Notification types are defined here. You can change/add types here. For example, to add another type type2 add the following block inside the select block:
<option value="type2">Type2</option>
The value must match that of the type defined in notificationType array in GCMCommonUtils class for the Client side implementation.

register.php

<?php

// response json
$json = array();

/**
 * Registering a user device
 * Store reg id in users table
 */
if (isset($_POST["regId"])) {
    //set default values
    $name = 'Anonymous';
    $email = 'anonymous@anonymous.com';
    $instanceId = '';
    $gcm_regid = $_POST["regId"];
    if(isset($_POST["name"])){$name=$_POST["name"];}
    if(isset($_POST["email"])){$email=$_POST["email"];}
    if(isset($_POST["instanceId"])){$instanceId=$_POST["instanceId"];}
    // GCM Registration ID
    // Store user details in db
    include_once 'db_functions.php';

    $db = new DB_Functions_GCM();
    if ($db -> checkUserById($gcm_regid) == false && $gcm_regid != "" && $gcm_regid != null) {

            $res = $db -> storeUser($gcm_regid,$instanceId,$name,$email);
            if(!$res){echo 'Failed to write to database';}

    } else {
     echo 'Invalid regId';
    }
} else {
 echo 'regId not given';
}
?>

You can use this file as it is. No modifications needed.


unregister.php

<?php

// response json
$json = array();

/**
 * Registering a user device
 * Store reg id in users table
 */
if (isset($_POST["regId"])) {
    $gcm_regid = $_POST["regId"];
    // GCM Registration ID
    // Store user details in db
    include_once 'db_functions.php';

    $db = new DB_Functions_GCM();

    if ($db -> checkUserById($gcm_regid) == true) {
        $res = $db -> deleteUserById($gcm_regid);

    }
} else {
    // user details missing
}
?>

You can use this file as it is. No modifications needed.

Put all those files under a directory named gcmsender. Thus your SERVER_URL (also your admin panel) will be http://yourdomain.com/gcmsender/ and you will send registration requests to http://yourdomain.com/gcmsender/register.php from your client app, unregistration requests to http://yourdomain.com/gcmsender/unregister.php from your client app. yourdomain.com may be localhost if you have done it in localhost.