How to Run a Cordova Application and Bring to Foreground on a GCM Notification

Cordova plugins

In the previous post, I tried to explain how to set up, receive and handle push notifications in a Cordova application. Back then, I believed that sending high priority notifications would make the app run immediately, even if it is not running at the moment. Unfortunately, it wasn't that easy. Here, I am going to describe my experience with editing Phonegap Push Plugin in order to achieve the needed behavior.

Warning: I am not an Android developer and had almost no Android / Java development experience. The reason of this article is to share my experiments and the way of thinking. Please think carefully before using code snippets below for production applications.

The main goal

As mentioned in introduction, I needed to achieve the behavior of waking up a Cordova app and process the notification payload. Setting notification’s priority to high was supposed to do this exactly in that order. There are two high priority options: setting "priority": "high" in the notification root object or "priority: 2" in the data object. The first one is counted by Google servers and is supposed to be delivered much faster; the latter is processed by the Phonegap Push Plugin and affects only on the order the notifications appear in list (the bigger number is, the higher it is shown). Unfortunately, both of these failed in running the app, when it was explicitly swiped from the recent list.

After doing some experiments with Skype, I noticed that incoming calls fire no matters what the Skype app’s state is, however it takes much longer to start ringing when Skype is swiped away. Viber can run itself as well. Its incoming messages are shown in a styled pop-up, which even disables the device’s lock screen. Both applications failed to notify after I force closed them in the applications settings. It seems like force closing stops any application code from running, so it can no longer process notifications until a user starts the application once again.

So, there should have been a way to wake up an app on notifications, and I was determined to find out how.

Inspecting Phonegap Push Plugin Java files

Before starting to edit Cordova plugin’s Java files, I want to tell you about one little nuance. When a new plugin is installed, its .java files are copied from plugins/<plugin name>/src/<platform> directory to platforms/<platform>/src/<android package name> directory. So in our case, the path for Phonegap Push Plugin is plugins/android/src/com/adobe/phonegap/push. When you re-run the app on your Android device or make an APK build, all the files are taken from there, not from the platforms folder. So edit them in the right place and don’t make my mistakes 😀. You might want to configure Android SDK with Android Studio or IDEA to be able to read the debugging messages. This process goes beyond the scope of this article, but you can check this awesome guide.

The first thing I was going to do was to determine which method is called when a new notification is received. After a short inspection of the project’s files, it seemed to be onMessageReceived in the GCMIntentService class. It checks if the application is in foreground and performs a corresponding action considering the force_show key (if force_show is true, a notification is shown even if the app being in foreground). In case of app being closed or sent to background, the last else statement is run, which leads to calling the showNotificationIfPossible(). This is the method we want to inspect in more details.

private void showNotificationIfPossible (Context context, Bundle extras) {
    // Send a notification if there is a message or title, otherwise just send data
    String message = extras.getString(MESSAGE);
    String title = extras.getString(TITLE);

    Log.d(LOG_TAG, "message =[" + message + "]");
    Log.d(LOG_TAG, "title =[" + title + "]");

    if ((message != null && message.length() != 0) ||
        (title != null && title.length() != 0)) {

        Log.d(LOG_TAG, "create notification");

        createNotification(context, extras);
    } else {
        Log.d(LOG_TAG, "send notification event");
        PushPlugin.sendExtras(extras);
    }
}

This is relatively simple. At the beginning, we require notification title and message attributes. If either of them is non-empty, we create a notification. Otherwise–simply pass it to the application callback. I thought this would be a good place to check for another custom parameter, and if it is present, somehow force application to start. To do it, I opened the file with the parameter list, PushConstants.java, and appended a new one:

public static final String FORCE_LAUNCH = "force_launch";

Apparently, now, if we include the force_launch parameter in our GCM message, it would be considered in the Java code and we’d be able to retrieve it like the following (some parts omitted for brevity):

private void showNotificationIfPossible (Context context, Bundle extras) {
	...
	String forceLaunch = extras.getString(FORCE_LAUNCH);
	if ((message != null && message.length() != 0) ||
		(title != null && title.length() != 0)) {

		Log.d(LOG_TAG, "create notification");

		createNotification(context, extras);
	}
	else if ("1".equals(forceLaunch)) {
		// Do some magic to run the app.
	}
	...
}

Okay, this seems to be working. We also probably need to check if the app is running in the background, but let’s let it be and concentrate on what can be done to start the application. In Android, every separate application screen is called an Activity. You usually switch from one activity to another by doing some action–say, tapping on a button. The Phonegap Push Plugin also has its activity, PushHandlerActivity, and if you inspect its onCreate method (as the name says, it runs on the activity creation), you’ll find the following interesting snippet:

if (!isPushPluginActive) {
    forceMainActivityReload();
}

This basically means: if there is no webview (app is closed), restart the MainActivity (relaunch the app). The description also confirms this: this activity will be started if the user touches a notification that we own. And that’s exactly what I needed! So at that point I knew that I’d have to emulate tapping on a notification and force the PushHandlerActivity to start.

After some investigation, I found out that communication between applications and activities in Android is done via Intent objects. With trial and error, I ended up with the following code in the showNotificationIfPossible method of GCMIntentService:

} else if ("1".equals(forceLaunch)) {
    Log.d(LOG_TAG, "force launch event");
    Intent intent = new Intent(this, PushHandlerActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
}

Luckily, we’re writing this inside a class that inherited from Context. So we can use this as a first argument to the Intent construction function. The second argument specifies the class name of the activity we’re intending to start. Apparently, an intent can have multiple flags that determine its final behavior. As the documentation says, FLAG_ACTIVITY_NEW_TASK will bring the specified activity to the foreground if it is already running. This sounds pretty good. The last line obviously starts the selected activity.

And that’s it. After re-running the Cordova application, I found that it works as expected. A notification with "force_launch": "1" in data runs my application if it was not running or quickly brings it to foreground. Awesome!

You can find the edited version of Phonegap Push Plugin on my Github fork.

Conclusion

To sum it up, I can say: don’t be afraid to experiment with things you haven’t worked with before. Just use common sense, logic and existing knowledge of general programming concepts, and sooner or later you’ll get what you need. It is not possible to know everything, but the ability to get along and achieve results with unfamiliar technologies is much more valuable than having deep knowledge in any single area.