teaching machines

CS 436 Lecture 25 – Push Notifications

November 25, 2014 by . Filed under cs436, fall 2014, lectures.

Push Notifications?

Push notifications are messages sent to an application that is not actively requesting them. From an operating systems perspective, the application is not polling for information, but is instead awaiting an interrupt. In a sense, push notifications are like phone calls or text messages. However, phone calls and text messages are typically used to interrupt another human. Push notifications are meant to interrupt an application.

These notifications are important to businesses whose apps you download. They can alert the user about upcoming events and sales, financial transactions, shipment tracking, and so on. A shopper might have a shopping list app that receives notifications when the shopper nears items she intends to purchase, something made possible by iBeacons and similar technology.

History

One of the earliest appearances of push notifications was in Research in Motion’s Blackberry devices. Their implementation reduced power consumption by centralizing the background listening that email apps would otherwise be doing. Microsoft and Apple implemented similar centralized listening in their mobile operating systems. Push notifications appeared in iOS 3, which was released in 2009. Google debuted its Cloud To Device Messaging (C2DM) service with Android 2.2. This was deprecated in 2012 and replaced by the Google Cloud Message (GCM) service.

Responsys reports that push notifications have significant of uptake in the mobile app market:

What’s more, of those who download a brand’s mobile app, 68% opt-in to receiving push notifications from that app, according to Responsys research.

Push notifications tend to consume cellular data instead of WiFi. WiFi tends to eat battery faster and is more likely to be turned off. On a phone, a cell connection is usually always on. However, many devices—especially tablets—do not have a cell connection. So, push notifications may be delivered over WiFi.

Google Cloud Messaging

We’ll implement push notifications today using Android. We’ll need Google Play Services, an API key for GCM, a registered client device, a server that will do the pushing, and a lightweight receiver of the notification.

There are many things about push notifications that we will not discuss (collapsing, expirations, message sizes, two way communication, and so on).

Google Play Services

GCM is an API that is provided by Google Play Services. We’ll need to include it in our project’s dependencies:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.android.gms:play-services:6.1.+'
}

Keys

We now register our project in the Google Developers Console. We create a project, add the Google Cloud Messaging for Android API, and create a new server key. We are prompted for the server’s IP address. We temporarily use 0.0.0.0/0 for testing.

The result is an API key, which we will need later—on the server, but not on the client.

Manifest

To receive push notifications, we request a permission in the manifest:

<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>

We must also declare and request a permission for receiving push notifications to this application:

<permission
    android:name="com.example.myapp.permission.C2D_MESSAGE"
    android:protectionLevel="signature"/>
<uses-permission android:name="com.example.myapp.permission.C2D_MESSAGE"/>

With the protectionLevel set to signature, only our app will be able to request this permission. No other app will be able to eavesdrop.

Registering with a server

For our app to receive notifications from GCM, it must opt-in. Thank goodness. Opting in requires the project number that we were assigned in the Google Developer Console. With that in hand, we register:

GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(MainActivity.this);
String clientID = gcm.register("MYPROJECTNUMBER");

This code must not run on the UI thread.

For a server to push a notification to us, it will need our client ID. The ID is rather long, so let’s just pop up a quick email that will represent us sending it to a server:

Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", "", null));
intent.putExtra(Intent.EXTRA_SUBJECT, "Client ID");
intent.putExtra(Intent.EXTRA_TEXT, clientID);
startActivity(intent);

The server should now store that client ID in a database, probably linking it to a customer.

Pushing to a client

Server here is loosely defined. We just need some entity to make a push notification request. We’ll use some Ruby for that. We start by importing some helper libraries:

require 'json'
require 'net/http'
require 'net/https'

We also declare a constant for our API key that we requested earlier and our client’s ID:

API_KEY = "..."
CLIENT_ID = "..."

To send a notification, we can issue an HTTP POST request with a JSON body containing our message’s data. Our destination is https://android.googleapis.com/gcm/send. We start by creating a connection and a request:

https = Net::HTTP.new('android.googleapis.com', 443)
https.use_ssl = true
request = Net::HTTP::Post.new('/gcm/send')

The request requires a couple of headers:

Content-Type: application/json
Authorization: key=API_KEY

We add these programmatically:

request['Content-Type'] = 'application/json'
request['Authorization'] = "key=#{API_KEY}"

Now, it’s time to form our message. Let’s send a math problem, a “daily derivative”:

payload = Hash.new
payload['registration_ids'] = [CLIENT_ID]
payload['data'] = { :message => "5 * x ^ 2" }

We ship it off:

request.body = payload.to_json
response = https.request(request)

With any luck, we find success. Let’s print out the result:

puts response.code
puts response.message
puts response.body

Creating a background listener

Back on the client, we’ve got to add some code that will get triggered when a push notification comes in. Since our app isn’t necessarily running, our Activity won’t be of much help. Instead, we use another pillar of the Android ecosystem: the BroadcastReceiver. We create a subclass:

public class NotificationReceiver extends BroadcastReceiver {
  @Override
  public void onReceive(Context context,
                        Intent intent) {
  }
}

We must announce its existence in the manifest. We add an intent filter so that when a push notification comes in for our app, our receiver gets notified. We also require that broadcasters have a SEND permission. Otherwise, we might get fake notifications from rogue apps.

<receiver
  android:name=".NotificationReceiver"
  android:permission="com.google.android.c2dm.permission.SEND">
  <intent-filter>
    <action android:name="com.google.android.c2dm.intent.RECEIVE"/>
    <category android:name="com.example.myapp"/>
  </intent-filter>
</receiver>

Back in onReceive, let’s write some handling code. When we get a push notification, let’s issue a device notification, which will appear in the UI:

public void onReceive(Context context,
                      Intent intent) {
  GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);
  String messageType = gcm.getMessageType(intent);

  if (messageType.equals(GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE)) {
    Log.d("FOO", intent.getExtras().toString());

    Notification.Builder builder = new Notification.Builder(context);
    builder.setSmallIcon(android.R.drawable.stat_notify_more);
    builder.setContentTitle("Daily Derivative");
    builder.setContentText(intent.getStringExtra("message"));

    NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    manager.notify(0, builder.build());
  }
}

Resources