LATEST VERSION: 1.9 - CHANGELOG
Push Notification Services v1.8

Android Push Client SDK

Sample App

You can find the Android Sample App on Github.

Version

This document covers the Android Push Client SDK v1.6.0.

There was no release of the Push Android SDK for v1.5.0.

Features

The Android Push Client SDK is a light-weight library that helps your app:

  1. Register for push notifications with Google Cloud Messaging (GCM) and an instance of the PCF Push Notification Service.

  2. Receive push messages sent via the same frameworks.

  3. Monitor geofences that have been configured from a central server.

Device Requirements

The Push SDK requires Android API level 16 or greater. Support for Android 14 and 15 was dropped as of Push SDK v1.4.0.

The Google Play Services app must be installed on the device before you can register your device or receive push messages. Typically, the user needs to be logged into a Google account as well. Most devices already have this app installed, but some odd ones may not. You should be able to receive push notifications on a Android emulated device if it has the Google APIs installed.

Required Setup

Getting Started

To receive push messages from the PCF Push Notification Service in your Android app, you need to create a project within the Google Developers Console. See Google Developers Console below.

Set up your app and an Android Platform on the PCF Push Notification Service Dashboard. This task is beyond the scope of this document, but note that you need the API Key parameter from Google Cloud Console above. After setting up your Android platform in PCF Mobile Services, note down the Platform UUID and Platform Secret parameters. You need them below. At this time, the Android Push software makes no distinction between developer and production modes.

For information on how to create your app and platforms, see Using the Dashboard.

Download the PCF Push Client SDK for Android from Pivotal Network. The Client SDK is delivered as an Android Library (i.e.: an “AAR” file). Copy the AAR file into the libs directory of your project and ensure that the following line line is in the dependencies section of your module-level build.gradle file:

    repositories {
        mavenCentral()
        flatDir {
            dirs 'libs'
        }
    }

Additionally, add the following dependency to the dependencies section of your module-level build.gradle file:

    dependencies {
        compile(name:'PCFPush-1.6.0', ext:'aar')
        compile 'com.google.code.gson:gson:2.4'
        compile 'com.google.android.gms:play-services-location:8.4.0'
        compile 'com.google.android.gms:play-services-gcm:8.4.0'
        compile 'com.android.support:support-annotations:23.3.0'
        compile 'com.android.support:appcompat-v7:23.3.0'
    }

You need to define and use the following permission element in the manifest element of your app’s AndroidManifest.xml file. Ensure that the base of the permission name is your app’s package name:

    <permission
        android:name="[YOUR.PACKAGE.NAME].permission.C2D_MESSAGE"
        android:protectionLevel="signature" />

    <uses-permission android:name="[YOUR.PACKAGE.NAME].permission.C2D_MESSAGE" />

You need to add the following receiver to the application element of your app’s AndroidManifest.xml file. Ensure that you set the category name to your app’s package name:

    <receiver
        android:name="io.pivotal.android.push.receiver.GcmBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND">
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE"/>
            <category android:name="[YOUR.PACKAGE.NAME]"/>
        </intent-filter>
    </receiver>

Configuration: Set Up Your pivotal.properties File

Create a pivotal.properties file in your project’s src/main/assets or src/main/res/raw directory. The following properties are required:

Property Required Description
pivotal.push.serviceUrl Yes The URL of the PCF Push Server.
pivotal.push.platformUuid Yes The platform UUID of your push platform on the PCF Push server.
pivotal.push.platformSecret Yes The platform secret of your push platform on the PCF Push server.
pivotal.push.gcmSenderId Yes The project number assigned by Google Cloud Console.
pivotal.push.sslCertValidationMode No Can be set to default, trustall, pinned, or callback. More details below in the SSL Authentication section.
pivotal.push.pinnedSslCertificateNames No If using pinned SSL validation mode then this property should be a list of SSL certificates in the DER format stored in the assets directory. The list is space separated.
pivotal.push.areAnalyticsEnabled No Set to false to disable the capture of push analytics data. Defaults to true.
  • None of the above values may be null. None of the above values may be empty.

  • The pivotal.push.platformUuid and pivotal.push.platformSecret parameters are the platform UUID and secret values from the Push Dashboard. If you use the SDK v1.6, then use UUID and secret of platform type Android. If you use the SDK v1.7, then use the type Android-FCM.

  • For instructions on how to convert your PEM certificate files to DER, see the OpenSSL documentation.

  • Note that the pivotal.push.trustAllSslCertificates property was removed in PCF Push Client SDK v1.3.3.

Registration

It is recommended that you initialize the Push Client SDK in your app’s primary Activity subclass’ onCreate method.

Add the following lines of code to the initialization section of your app. You need a Context object to pass to the getInstance method, so you should try to add this code to your Activity class. In the example below the Context is the this object passed to the getInstance method (assuming that we’re in an Activity):

    try {
        // RegistrationListener is optional and may be `null`.
        Push.getInstance(this).startRegistration(DEVICE_ALIAS, CUSTOM_USER_ID, TAGS, ARE_GEOFENCES_ENABLED, new RegistrationListener() {

            @Override
            public void onRegistrationComplete() {
                Log.i("MyLogTag", "Registration with PCF Push successful.");
            }

            @Override
            public void onRegistrationFailed(String reason) {
                Log.e("MyLogTag", "Registration with PCF Push failed: " + reason);
            }
        });
    } catch (Exception e) {
        Log.e("MyLogTag", "Registration with PCF Push failed: " + e);
    }

The DEVICE_ALIAS is a custom field that you can use to differentiate this device from others and is intended for future use. If you don’t want to use the device alias then you can set this argument to null or an empty string. At this time you can not use the device alias for targeting push notifications. We recommend that you use the user’s device name to populate this field.

The CUSTOM_USER_ID is another custom field that you can use to associate this device with the user. It is possible to target push notifications to custom user IDs. If you don’t want to use the custom user ID then you can set this argument to null or an empty string. Custom user IDs are treated as case-sensitive.

The TAGS parameter is a Set<String> of tags that your app would like to subscribe to. There are many possible uses of tags but they are dependent on your particular use cases. Always ensure that you provide all of the tags that you’d like to be subscribed to; if you omit tags in future calls to the register method then the SDK thinks that you are trying to unsubscribe from those tags. If there are no tags that you want to register to then you can set this argument to null. Tags are treated as case-insensitive.

The ARE_GEOFENCES_ENABLED is a boolean value that turns the geofences feature on and off (described below). If you want to use geofences in your app, then request permission to read the device location. If you want to support Android Marshmallow, you must write extra code to request the device location. This extra code is described in the geofences section below.

You should only have to call startRegistration once in the lifetime of your process – but calling it more times is not harmful. The startRegistration method is asynchronous and will return before registration is complete. If you need to know when registration is complete (or if it fails), then provide a RegistrationListener as the second argument.

Registration Examples

Example 1: Registering for Push Notifications with no options, tags, without geofences and with no callback.

    Push.getInstance(this).startRegistration(null, null, null, false, null);

Example 2: Registering for Push Notifications with a customer user ID using the user’s account name (for example).

    final String customUserId = "test@example.net"; // Your user's account name
    Push.getInstance(this).startRegistration(null, customUserId, null, false, null);

Example 3: Removing the registration for the custom user ID (which prevents the user from being targeted by their custom user ID).

    final String customUserId = ""; // Can use null or empty string to remove the custom user ID
    Push.getInstance(this).startRegistration(null, customUserId, null, false, null);

Example 4: Subscribing to several topics on a news service.

    final Set<String> tags = new HashSet<>();
    tags.add("breaking_news");
    tags.add("local_news");
    Push.getInstance(this).startRegistration(null, null, tags, false, null);

Example 5: Unsubscribing from the “breaking_news” tag while remaining subscribed to the “local_news” tag.

    final Set<String> tags = new HashSet<>();
    tags.add("local_news");
    Push.getInstance(this).startRegistration(null, null, tags, false, null);

Receiving Push Notifications

To receive push notifications in your app, you need to add a custom Service to your app that extends the GcmService provided in the SDK. The intent that GCM sends is passed to your service’s onReceiveMessage method. Here is a simple example:

    public class MyPushService extends GcmService {

        @Override
        public void onReceiveMessage(Bundle payload) {
            if (payload.containsKey("message")) {
                final String message = payload.getString("message");
                handleMessage(message);
            }
        }

        private void handleMessage(String msg) {
            // Your code here. Display the message
            // on the device's bar as a notification.
        }
    }

Finally, you need to declare your service in your AndroidManifest.xml file.

     <service android:name=".MyPushService" android:exported="false" />

Optional Items

Push Analytics

Version 1.3.3 of the PCF Push Client SDK supports the collection of some simple push analytics data:

  • Receiving push notifications
  • Opening push notifications
  • Triggering geofences

Analytics are enabled by default. You can disable it by setting the pivotal.push.areAnalyticsEnabled parameter in your pivotal.properties file to false. Ensure that you have an up-to-date version of the PCF Push API server and that it is generating receiptId data in the remote notifications that it generates (which is activated by default).

Since the notification capabilities on Android are very diverse the SDK doesn’t do any work to help apps display them. It relies on your app to decide how to display and handle all push notifications. As such, there is no way for the SDK to know when the user touches a notification and opens your app. If you want to collect metrics about how many users are opening the notifications in your app then the SDK relies on your app to inform it. You need to call the logOpenedNotification method in the Push class with the same Bundle that was delivered in the push notification.

The capturing push analytics data requires v1.3.2 of the Push API server. The SDK checks the server version before capturing any analytics data. If the server version is too old, then no analytics data is recorded. The SDK checks the server version once every 24 hours in release builds and every 5 minutes in debug builds.

e.g.:

Let’s say that you use this code to display a push notification in your subclass of GcmService:

    @Override
    public void onReceiveMessage(Bundle payload) {
        final String message = payload.getString("message");

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

        final Intent intent = new Intent(this, MyAppsMainActivity.class);
        intent.setAction("YOUR_CUSTOM_NOTIFICATION_ACTION_NAME");
        intent.putExtras(payload);
        final PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, 0);

        final NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
            .setSmallIcon(R.drawable.ic_your_app_logo)
            .setContentTitle(getString(R.string.app_name))
            .setContentIntent(contentIntent)
            .setContentText(msg);

        notificationManager.notify(NOTIFICATION_ID, builder.build());
    }

Then you can use the following code in the opened activity to report that the notification has been opened:

    public class MyAppsMainActivity extends Activity {
    ...

    @Override
    protected void onResume() {
        super.onResume();

        final Intent i = getIntent();
        if (i.getAction().equals("YOUR_CUSTOM_NOTIFICATION_ACTION_NAME")) {
            Push.getInstance(this).logOpenedNotification(i.getExtras());
        }
    }

Note that it is important to pass the entire remote notification payload Bundle into the logOpenedNotification method. This example accomplishes this requirement by saving the payload Bundle in the Intent Extras in the PendingIntent passed to the notification.

Tags

If any of your tags change during the lifetime of your process (e.g.: your app wants to change the list of tags that it has subscribed to) then call subscribeToTags with your new set of parameters. Example:

    // The SubscribeToTagsListener is optional and may be `null`.
    Push.getInstance(this).subscribeToTags(TAGS, new SubscribeToTagsListener() {
       @Override
       public void onSubscribeToTagsComplete() {
           Log.i("MyLogTag", "Successfully subscribed to tags with PCF Push.");
       }

       @Override
       public void onSubscribeToTagsFailed(String reason) {
           Log.e("MyLogTag", "Failed to subscribe to tags with PCF Push:" + reason);
       }
    });

Unregistration

If you want to unregister from push notifications then you can call the startUnregistration method:

    // The UnregistrationListener is optional and may be `null`.
    Push.getInstance(this).startUnregistration(new UnregistrationListener() {
        @Override
        public void onUnregistrationComplete() {
            Log.i("MyLogTag", "Successfully unregistered from PCF Push.");
        }

        @Override
        public void onUnregistrationFailed(String reason) {
            Log.e("MyLogTag", "Failed to unregister from PCF Push: " + reason);
        }
    });

Reading the Device UUID

In order to target individual devices for remote notifications using the PCF Push Notification Service, you need to target the Device UUID assigned to each device by the service. You can read the Device UUID at run time any time after a successful registration with the service by calling the getDeviceUuid method. This method returns null if the device is not currently registered with the PCF Push Notification Service.

Example:

    Push.getInstance(this).startRegistration(deviceAlias, subscribedTags, areGeofencesEnabled, new RegistrationListener() {

        @Override
        public void onRegistrationComplete() {
            Log.i("MyLogTag", "Device Uuid: " + Push.getInstance(this).getDeviceUuid());
        }

        @Override
        public void onRegistrationFailed(String reason) {
            Log.e("MyLogTag", "Failed to unregister from PCF Push: " + reason);
        }
    });

SSL Authentication

The property pivotal.push.sslCertValidationMode allows the app to accept the following supported SSL Authentication modes:

  1. default : When the service URL is not HTTPS or when using a server trusted certificate this mode should be set.

  2. trustall : When using a development environment there is the ability to trust all certificates while using a HTTPS service URL. This mode replaces the previous property (prior to v1.3.3) pivotal.push.trustAllSslCertificates.

  3. pinned : To ensure no man in the middle attacks this mode should be set. The server certificate is verified with the local copy of the certificate referred to as Certificate Pinning authentication. When this mode is set the local copy of the certificate(s) should be provided with a space-separated list in the pivotal.push.pinnedSslCertificateNames property. All certificates provided are stored in the assets folder of the app in a DER format.

  4. callback : When a custom SSL authentication schema is required this mode can be set whereby the specific authentication logic would be added inside the app as a callback to the SDK. You need to create your own implementation of a class extending the CustomSslProvider interface and declare it in your manifest file in a <meta-data> element in your <application> element. The name of the meta-data is “io.pivotal.android.push.CustomSslProvider” and the value of the meta-data should be the name of your custom SSL provider class (with its full package name). This class must have a default (empty) constructor and is instantiated at runtime when network requests are made to HTTPS service endpoints.

example CustomSslProvider implementation:

    public class MyCustomSslProvider implements CustomSslProvider {

        public MyCustomSslProvider() { /* default constructor is required */ }

        @Override
        public SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException, KeyManagementException {

            TrustManager[] trustAllCerts = new TrustManager[] { FILL ME IN };

            SSLContext context = SSLContext.getInstance("TLS"); // or "SSL" - please look at the Java documentation
            context.init(null, trustAllCerts, null);

            return context.getSocketFactory();
        }

        @Override
        public HostnameVerifier getHostnameVerifier() {
            return new HostnameVerifier() {
                public boolean verify(String hostname, SSLSession session) { FILL ME IN }
            };
        }
    }

example AndroidManifest.xml:

    <application>

        ...

        <meta-data
            android:name="io.pivotal.android.push.CustomSslProvider"
            android:value="YOUR PACKAGE NAME.MyCustomSslProvider"/>

        ...

    </application>

Setting Custom HTTP Request Headers

In order to inject custom headers into any HTTP requests made by the Push SDK you should call the setRequestHeaders method in the Push class with a Map<String, String> of the required HTTP header values. Note that you can not provide any ‘Authorization’ or 'Content-Type’ headers via this method; they are ignored by the Push SDK.

In order for this method to take effect you need to call it before startRegistration, subscribeToTags, or any other methods that make network requests.

Geofences

Geofences are newly supported in v1.3.0 of the Push Notification Service. Using this service, you can register push notifications that your app users see when they enter or exit certain geographic regions that you define on the Push Notification Service Dashboard.

To set up your app to receive geofence notifications, perform the following steps.

Step 1: Set Up Your AndroidManifest.xml File

Add these two permissions to the application element of your AndroidManifest.xml file.

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

Step 2: Set Up Your Push Service

You need to override the following two methods in your app custom Service (see Step 7 above).

    @Override
    public void onGeofenceEnter(Bundle payload) {
        Log.i("My Log Tag", "Entered geofence " + payload.getString("message"));
            // Process geofence enter event
    }

    @Override
    public void onGeofenceExit(Bundle payload) {
        Log.i("My Log Tag", "Exited geofence " + payload.getString("message"));
            // Process geofence exit event
    }

Step 3: (Optional) Receive Geofence Status Updates

The PCF Push Notification Service server pushes updated geofences to user devices via push notifications. You don’t need to do any more work to process these updates or monitor these geofences. You can read the geofence status object to find out if any problems occur during these background updates. These errors can be reported directly to your app if you create a BroadcastReceiver that listens to io.pivotal.android.push.geofence.UPDATE intents.

Example:

Create a class called MyGeofenceUpdateBroadcastReceiver:

    public class MyGeofenceUpdateBroadcastReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            final GeofenceStatus status = Push.getInstance(context).getGeofenceStatus(); // Read geofence status
            if (status != null) {
                if (status.isError()) {
                    Toast.makeText(context, status.getErrorReason(), Toast.LENGTH_LONG).show();
                }
                Toast.makeText(context, "Number of currently monitoring geofences: " + status.getNumberCurrentlyMonitoringGeofences(), Toast.LENGTH_LONG).show();
            }
        }
    }

You can configure your BroadcastReceiver class to listen to geofence updates by adding the following element in your AndroidManifest.xml:

    <receiver
        android:name=".MyGeofenceUpdateBroadcastReceiver" android:exported="false" >
        <intent-filter>
            <action android:name="io.pivotal.android.push.geofence.UPDATE"/>
        </intent-filter>
    </receiver>

Step 4: Request device location permission (Android v6.0 Marshmallow and up)

Android v6.0 Marshmallow introduced a new system for obtaining user permission for “dangerous” operations. If you want to use geofences in your app then you need to request the permission to read the device location at runtime. Before Android v6.0 Marshmallow it was sufficient to simply add a uses-permission element to your AndroidManifest.xml file in order to request permission as described in Step 1 above. In Android v6.0 Marshmallow you must still add the uses-permission element to your AndroidManifest.xml file but you must also request permission from the user directly at runtime. We’ve added a helper method to the Push SDK to help you with this task but you still need to do some of the work yourself in your app.

In one of your app’s primary Activity classes, you need to add the following code to your onCreate method BEFORE you initialize the Push SDK. The dialog box must contain a message that explains to your user why your app needs to read the device location. You may style or theme this dialog box any way that you would like to. You only need to give the dialog box one button: “OK”.

    if (ARE_GEOFENCES_ENABLED) {

        // If you want to use geofences and are targetting Android Marshmallow or greater, then you must specifically
        // ask the user for permission to read the device location.  The following Dialog class is used to explain
        // to the user why your app is requesting permission to read the device location.

        final Dialog dialog = new AlertDialog.Builder(this)
                .setMessage("This application needs permission to read the device location in order to send you notifications when you enter certain locations.")
                .setPositiveButton("OK", null)
                .create();

        final boolean werePermissionsAlreadyGranted = Push.getInstance(this).requestPermissions(this, REQUEST_PERMISSION_FOR_GEOFENCES_RESPONSE_CODE, dialog);

        if (werePermissionsAlreadyGranted) {

            // If Push.requestPermissions returns true then ACCESS_FINE_LOCATION permission has already been granted
            // and we can immediately begin push registration.

            startPushRegistrationWithGeofencesEnabled(true);
        }

    } else {
        startPushRegistrationWithGeofencesEnabled(false);
    }

If the permission to read the device location has not yet been granted, then Google shows a system dialog box to request permission. It may also show your user-defined dialog box. After the user presses “Allow” or “Deny” then Google calls the onRequestPermissionsResult callback in the same activity:

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        // This callback is invoked by Android after the user decides to allow or deny permission for ACCESS_FINE_LOCATION.
        // If Push.requestPermissions returns false then you need to wait for this callback before attempting
        // to register for pushes.

        if (requestCode == REQUEST_PERMISSION_FOR_GEOFENCES_RESPONSE_CODE && permissions[0].equals(android.Manifest.permission.ACCESS_FINE_LOCATION)) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                startPushRegistrationWithGeofencesEnabled(true);
            } else {
                startPushRegistrationWithGeofencesEnabled(false);
            }
        }
    }

The REQUEST_PERMISSION_FOR_GEOFENCES_RESPONSE_CODE value is a unique integer that is echoed back to the onRequestPermissionsResult method after the user allows or denies the permission. You can select any integer that you would like.

    // Request code when requesting permission to use geofences.
    private static final int REQUEST_PERMISSION_FOR_GEOFENCES_RESPONSE_CODE = 27; // Your favourite integer

Step 5: Enable geofences

In order to enable geofences at runtime you need to pass true to the areGeofencesEnabled argument when you call the startRegistration method in your app main activity. If this parameter is set to false then no geofences features are available at runtime. Any geofences that may have been monitored before are cleared and are no longer monitored.

The startPushRegistrationWithGeofencesEnabled method in the above example will finally initialize the Push SDK. If the device location permission was not granted then you should disable geofences. Note that the user is able to allow or revoke this permission at any other time in the future. It is important to request this permission EVERY TIME you initialize your Push SDK:

    private void startPushRegistrationWithGeofencesEnabled(boolean areGeofencesEnabled) {

        Push.getInstance(this).startRegistration(DEVICE_ALIAS, TAGS, areGeofencesEnabled, new RegistrationListener() {

            @Override
            public void onRegistrationComplete() {
                printMessage("Registration successful.");
            }

            @Override
            public void onRegistrationFailed(String reason) {
                printMessage("Registration failed. Reason: " + reason);
            }
        });
    }

Appendix

Google Developers Console

  1. Log into Google Developers Console. You need a Google account.

  2. Click Create Project.

  3. Enter a Project Name and leave the auto-generated Project ID field untouched. Click Create.

  4. Wait until the project is completed, this might take a couple of minutes. After this, you are on the project page.

  5. Note at the top your Project Number. This value should be in light gray text. Make note of this value because you need it later. Make sure you use the numeric project number. Do not use the project ID with the words.

  6. On the left, in the APIs & Auth section, click APIs.

  7. In the Browse APIs field, enter Google Cloud Messaging and ensure that Google Cloud Messaging for Android is enabled by clicking Enable API.

  8. On the left click the Credentials link which is directly below the APIs link.

  9. Find Public API Access on the page and click the Create new Key button below. Click Server key when the dialog pops up.

  10. In the text field inside the dialog box enter 0.0.0.0/0 and click the Create button.

  11. Make note of the API KEY value because you need it later.

Troubleshooting

See Troubleshooting.

Create a pull request or raise an issue on the source for this page in GitHub