Intents with Qt for Android, part 1

The "intent" is the main facility for interprocess communication on Android. Basically, an intent is an object that is processed by the operating system and then passed to one or more of the installed applications based on its contents. It could for instance be a request to show a video, in which case the intent type would be ACTION_VIEW and the mime type would be set accordingly. Applications can subscribe to certain intents by setting an intent filter in their manifest files. The first time a type of intent is seen, the user of the device will be presented with a selection of applications known to subscribe to that type of intent. If they choose, they can set a default at this point, or select to be asked for every instance.

This is the first of a few articles showing how to use intents with Qt for Android.

The mechanism itself is quite general. What is described above is one use case, but intents are also used for other things: One such thing is launching services inside the application. This is usually formatted as an "explicit intent", meaning that the fully qualified name of the service to start is provided, so it cannot be intercepted by any other applications.

Another way intents are used is for broadcasts, for instance when the time zone of the device changes. While the action to view a video described above would only launch the one specific application selected by the user, a broadcast will rather be passed to any application which subscribes to it.

This blog will focus on Android, but a similar mechanism called app extensions was introduced in iOS 8.

In this first article about intents on Android, I will focus on the simplest use case: Creating an implicit intent from Qt to start an unspecified application on the target device. As an example, I have made a simple application which presents food recipes. Each recipe has an estimated time for completion. By clicking a button, you can set a timer using the preferred countdown application on the device. (For a more useful use case, one might imagine a button setting a timer for each of the steps in the recipe's directions, but for this simple example we will only have a single timer per recipe.)

I won't go into detail about how the application itself is written, but the code can be found here. No effort has been made to make the UI look nice, so please disregard that. The application is backed by an SQLite database (which is filled with some dummy content the first time the application is started) and has a very simple UI written in Qt Quick Controls 2, allowing the user to select a recipe from a list, see its details on a separate page and click a button on the recipe to set a timer in the system.

Picture of recipe

The part we will focus on is the code that actually requests the timer from the system. This is in the recipe.cpp file, in the function Recipe::createTimer(). For our example we will be using the AlarmClock.ACTION_SET_TIMER intent type.

We will use the JNI convenience APIs in the Qt Android Extras module for this. I will go through this code step by step.


    QAndroidJniObject ACTION_SET_TIMER = QAndroidJniObject::getStaticObjectField("android/provider/AlarmClock",
                                                                                          "ACTION_SET_TIMER");
    QAndroidJniObject intent("android/content/Intent",
                             "(Ljava/lang/String;)V",
                             ACTION_SET_TIMER.object());

The first step is to create the intent. We retrieve the identifier of the ACTION_SET_TIMER intent. This is a static member of the android.provider.AlarmClock class and is of type String. Note the use of slashes in place of dots in the package name of the class. This corresponds to the bytecode representation of package names (for historical reasons according to the specification) and is therefore also used in signatures in JNI.

Since we are only using functions from Qt Android Extras and no explicit calls using the JNI APIs, we don't have to worry about reference management. This is one primary convenience of the APIs.

The next step is to actually create the intent object. We do this by constructing a QAndroidJniObject, passing in the JNI-mangled signature of the Java constructor we want to call and we pass in the ID of the action we just got from the AlarmClock class.


    QAndroidJniObject EXTRA_MESSAGE = QAndroidJniObject::getStaticObjectField("android/provider/AlarmClock",
                                                                                       "EXTRA_MESSAGE");
    QAndroidJniObject messageObject = QAndroidJniObject::fromString(message);
    intent.callObjectMethod("putExtra",
                            "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;",
                            EXTRA_MESSAGE.object(),
                            messageObject.object());

QAndroidJniObject EXTRA_LENGTH = QAndroidJniObject::getStaticObjectField("android/provider/AlarmClock", "EXTRA_LENGTH"); intent.callObjectMethod("putExtra", "(Ljava/lang/String;I)Landroid/content/Intent;", EXTRA_LENGTH.object(), jint(m_time * 60));

QAndroidJniObject EXTRA_SKIP_UI = QAndroidJniObject::getStaticObjectField("android/provider/AlarmClock", "EXTRA_SKIP_UI"); intent.callObjectMethod("putExtra", "(Ljava/lang/String;Z)Landroid/content/Intent;", EXTRA_SKIP_UI.object(), jboolean(true));

Next up, we set up some arguments we want to send to the activity handling the action. We pass in a description of the timer and the number of seconds to set it for.


    QAndroidJniObject activity = QtAndroid::androidActivity();
    QAndroidJniObject packageManager = activity.callObjectMethod("getPackageManager",
                                                                 "()Landroid/content/pm/PackageManager;");
    QAndroidJniObject componentName = intent.callObjectMethod("resolveActivity",
                                                              "(Landroid/content/pm/PackageManager;)Landroid/content/ComponentName;",
                                                              packageManager.object());

Then we have to resolve the activity. This will tell the system to check if there is an application available to handle the ACTION_SET_TIMER action. We first get a reference to the current activity (our current context) in order to get the appropriate package manager. Then we call resolveActivity() on the intent and pass in the package manager.


    if (componentName.isValid()) {
        QtAndroid::startActivity(intent, 0);
    } else {
        qWarning() << "Unable to resolve activity";
    }

If the returned component name is non-null, we start the activity using the convenience function in Qt Android Extras. We will look at more features of this function in a later blog. If no appropriate activity is found, then this code only outputs a warning to the console. A proper application might complain in the actual user interface, but note that the operating system has also alerted the user of the problem at this point.

Screenshot of alarm that has been set

That's it for this first introduction to using intents in Qt for Android. Look out for more complex examples in the future.


Blog Topics:

Comments