Integrating Plastic SCM with Google Cloud Messaging

Thursday, September 03, 2015 1 Comments

Google Cloud Messaging is, for those of you who don't know it, a free service that allows developers to send real time push notifications across different platforms, like Android, iOS, and Google Chrome. It is reliable, easy to implement both client and server sides, and it's fast.

So, while I was implementing GCM in a pet project of mine, it came to my mind that it could be useful for someone to have in their handheld device, or in their browser, real-time updates about what is happening in their team through their favorite DVC software.

One of the features I like the most in Plastic SCM are triggers. They empower you to easily extend Plastic functionality. So they were the obvious point to start as they are triggered after specific actions are executed in the server or in the client sides. If you want more information about triggers you can find the official guide here.

Because I want a clean as short as possible trigger code, and I have some background in the Android platform, my languages of choice for this demonstration are Python and Java. You'll find GitHub links to the full mobile app and trigger implementation at the end.

To follow this guide you'll need to meet this prerequisites:

  1. A Plastic SCM setup with the cm utility available, and the correct permissions to add client or server sides triggers.
  2. A Python installation in the client/server side (depends on the kind of triggers you want to implement). We'll use the handy py-gcm module to handle communications with the Google's backend. So you must be able to install modules too.
  3. Android Studio to develop the mobile app. Here we'll see some code snippets to handle registration against GCM and retrieving the unique device ID. You may be tempted to use Eclipse with the ADT plugin. Please, just don't. Android Studio is now the de facto IDE for Android developers.

So, I'll start the post with the final result. At the end, I'll be able to add a trigger like this:

$ cm maketrigger after-mkbranch "GoogleCloudBranch" "python /opt/triggers/cloud_trigger.py \"New branch created\" \"%{USER} created the branch %{BRANCH} in %{REPNAME} with the following comment: %{COMMENT}\""

...and I'll receive in my smartphone a notification like this every time somebody creates a new branch:

The Android application

What our application must be able to do is quite simple:

  • listen to GCM push notifications
  • extract the bundled contents
  • build a notification
  • show it to the user

Additionally, it should show us the GCM registration ID in order to have it in the trigger. In an ideal world, our application would register itself against a backend of us, making the ungrateful task of copying a long alphanumeric string automatic, and keeping it up to date (the ID can expire!). But that is up to you (let us know if you went that far!).

If you want to start the application by scratch, I'll walk you through the most important parts of the code. Remember that all the code is available online.

Adding dependencies

We'll start by adding to our project-level build.gradle file the dependency com.google.gms:google-services:1.3.0-beta1, right under the com.android.tools.build one:

Now, in our module-level (usually app) build.gradle file, the com.google.gms.google-services plugin after the com.android.application one:

And in the dependencies section, all the necessary ones to work with GCM. Google Play Services were splitted in a lot of different libraries to avoid the dex limit of 65536 methods, so it's important to add both of them:

compile 'com.google.android.gms:play-services:7.8.0'
compile 'com.google.android.gms:play-services-gcm:7.8.0'

As you can see, there are other dependencies like ButterKnife and EventBus. Although they are not mandatory, developing for Android is much easier with them. The first one avoids all the necessary boilerplate to find views and attach listeners to them. And the second one allows us to send events avoiding callbacks.

If Android Studio warns you that there are newer versions available, then use them. If you face any problem try the versions displayed here.

Here I use the 23rd version of the support libraries. Right now they are final versions, with Android Marshmallow (API 23) final SDK released, but if you have any problem compiling with them, then roll back to 22.2.1.

Now, before writing any code, we need some data from Google. Choose now a package name and stick with it. I chose com.codice.notifier. You'll need to complete the steps here, remembering to choose "Cloud Messaging" at the "Choose services" step. They are pretty straight-forward, so if you have any doubt let us know in the comments. Remember to write down the Server API Key and the Sender ID values. You'll find those later in your Google Developers Console.

At the end, you should have a JSON file. Copy it to the app/ directory, and create a new entry in the app/src/main/res/values/strings.xml file with the "Application sender id" value:

<string name="gcm_default_sender_id">665xxxxxxxxx</string>

Implementing some basic client code

It's time to code! We need to implement four different classes, but all of them are quite small.

The first step is registering the application against Google Cloud. The GCM regId is, as I said, an alphanumeric string that identifies a single installation of an app in a single device. So, in the end, it allows a fine-grained control on where a notification is sent. Because networking is involved, Android won't allow doing the registration directly in an Activity: the system will kill our app if we do that. So we'll follow Google's recommendation and do it in a class that extends IntentService. Defining the constructor of this class is tricky mainly because of lack of documentation and Lint's misleading warnings, so be careful.

public class RegistrationIntentService extends IntentService {

    public RegistrationIntentService() {
       super("RegistrationIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
        try {
            String deviceId = gcm.register(getString(R.string.gcm_default_sender_id));
            SharedPreferences.Editor defaultEditor =
                PreferencesManager.getDefaultSharedPreferences(this).Edit();
            defaultEditor.putBoolean("registered", true);
            defaultEditor.putString("gcm_regid", deviceId);
            defaultEditor.apply();
        } catch (IOException e) {
            Log.e("RegistrationIntentService", "IOException: " + e.getLocalizedMessage());
            e.printStacktrace();
        }
    }
}

We won't need any fancy storage to save the device ID and the boolean that will indicate if the app must perform a registration again, so SharedPreferences will do just fine. In the end, this data will be saved as a XML file in a protected directory.

Now, it is mandatory to create a class that extends InstanceIDListenerService. It's the onTokenRefresh() method that will be called by GCM when our token expires, so we’ll just add the necessary boilerplate:

public class CloudIDListenerService extends InstanceIDListenerService {

    @Override
    public void onTokenRefresh() {
        Intent i = new Intent(this, RegistrationIntentService.class);
        startService(i);
    }
}

The next step is to implement a class extending the GcmReceiver one. This is a key step because this GcmReceiver extends the WakefulBroadcastReceiver, which is the service able to wake up the processor to receive notifications even when our phone is with the screen off or in a deep-sleep state (comming in Android Marshmallow 6.0). We won’t customize it, as it is not necessary:

public class CloudMessagingReceiver extends GcmReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
    }
}

And ending with the GCM services, the one where the action happens. A class that extends GcmListenerService where we will receive (in the form of a Bundle) the data we send from the Plastic SCM trigger:

public class CloudListenerService extends GcmListenerService {

@Override
    public void onMessageReceived(String from, Bundle data) {
        super.onMessageReceived(from, data);

        String title = data.getString("title");
        String message = data.getString("message");

        Utils.triggerNotification(this, title, message);
}

The Utils.triggerNotification(Context context, String title, String message) method is just where the notification is built and shown. In the Google Developer website you can find a good guide to build your own notifications.

We should add some code to check if the device has an updated version of the Play Services APK, but chances are, there will be one (remember that this code won't run on any device without the Google apps, like Amazon's Android fork).

Setting up permissions

We're ready to add to our manifest the required permissions. Permissions in Android grant our application access to such things as the Internet or being able to wake up the device. If we tried to access a protected resource without the required permission, the system would kill our application instantly and without any kind of explanation to the user.

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

The first one will grant us access to the internet. The second one will allow the application to acquire wake locks, required to keep the processor from sleeping when it receives a notification. The third one is necessary in order to register and receive the GCM notifications, and the last one will allow us to make the phone vibrate when a notification is received.

Now we have to declare and use our own permission to prevent another application from registering for our notifications. Be careful with the package.name.permission.C2D_MESSAGE structure:

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

Declaring the correct intent filters for the classes is a little bit complicated. Some of them must be exported, others don’t... I invite you to check out my AndroidManifest.xml on GitHub. Look for the "Google Cloud Messaging" comments.

The trigger

What the trigger must do is actually really simple. If you didn't read the official trigger's guide, I'll make you a quick digest. Before Plastic SCM runs the trigger, it sets up some OS-wide environment variables with relevant information about what just happened. For example, if somebody creates a new branch, Plastic will set up some variables with the partial and full branch name, the name of the creator, the name of the repository, and the creation comment, along with other common variables.

We will set our trigger with two arguments: the notification title and the body text (in which we will write some keywords like %{COMMENT}). The trigger will look for all the possible environment variables that Plastic can set, and will substitute in our text the keywords with the values found, using the os module available in Windows, OS X and GNU/Linux. This way, it doesn't matter our language of choice for the trigger nor our platform, as long as we have access to those variables.

The next step will be to send the actual notification. The trigger code is a bit long to put it here, but I'll write a short one:

from gcm import GCM

def main():
    cloud = GCM('AlzaSyxxxxxxxxxxxxx')
    ids = ['APA91bH0...', 'device-2', ... ]
    notification = {"title": "Notification's title",
                    "message": "Notification's body",
                    "authorized": False,
                    "count": 99}
    cloud.json_request(registration_ids=ids, data=notification)

if __name__ == '__main__':
    main()

The py-gcm module handles the communication with the GCM backend. The Bundle object we will receive in our Android app (remember onMessageReceived(String from, Bundle data) from before?) will contain the notification dictionary content, so you can customize that to fit your needs. Before, we looked only for a title and a message, but now we have a authorized and a count fields:

boolean authorized = data.getBoolean("authorized");
int count = data.getInt("count");

Putting it all together

We have some things left to do: compile and install the application. You can use, as you imagined, Android Studio. You must enable the developer mode on your phone first:

If you run Windows, you may encounter some trouble with the USB driver. Google can help you with that.

Then, you must customize the trigger with your GCM API key and devices IDs, and set it up in Plastic. The following example is for a Linux system. Be really carefull with the quotation marks:

$ cm maketrigger after-mkbranch "GoogleCloudBranch" "python /opt/triggers/cloud_trigger.py \"New branch created\" \"%{USER} created the branch %{BRANCH} in %{REPNAME} with the following comment: %{COMMENT}\""

At last, if you plan to use this on a sensitive server remember to keep the API key safely stored. And maybe it's not a bad idea to set a white list of IPs on your Developer Console to avoid notifications to be sent from elsewhere in case the API Key is compromised.

If you had any problem following the post, reading the code or running the samples, please, let us know. Hope you found it useful!

1 comment:

  1. Wow this is fantastic !! it is a new kind of social coding activity.

    ReplyDelete