QtJenny: Generating C++ proxy classes to access Android APIs

Demo showcasing the use of QtJenny.

Overview

This demo showcases the use of QtJenny and uses it to generate C++ proxy classes to access Android APIs from C++ code. The generated C++ classes are used in the demo to perform actions such as adjusting the volume and brightness, activating and deactivating wake locks, sending notifications and triggering vibrations. These actions are part of Android APIs that Qt doesn't implement.

Generating C++ classes using QtJenny removes the need to write JNI code manually.

How it works

The demo contains two parts, a Qt project called qtjenny_consumer and an Android Studio project called qtjenny_generator. The qtjenny_consumer contains the application UI and the code using the generated C++ headers. The qtjenny_generator contains the class annotations and Gradle configurations for QtJenny.

To launch the demo, you need to run the qtjenny_consumer project. During the CMake configuration of qtjenny_consumer the code generation is triggered automatically by a call to execute a gradlew task in the qtjenny_generator project directory.

 if (ANDROID)
     if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
         set (gradlew_cmd "gradlew.bat")
     else()
          set (gradlew_cmd "./gradlew")
     endif()
     set (gradlew_arg "--rerun-tasks")
     set (gradlew_task "kaptReleaseKotlin")
     execute_process(COMMAND ${gradlew_cmd} ${gradlew_arg} ${gradlew_task}
     WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/qtjenny_generator")
 else()
     message(FATAL_ERROR "Example only works on Android")
 endif()
Generating the C++ headers

Generation of the C++ headers starts when qtjenny_generator is built, this triggers an annotation processor that processes the annotations in GenerateCppCode.kt.

 @NativeClass
 @NativeProxy(allMethods = false, allFields = false)
 @NativeProxyForClasses(namespace = "android::os", classes = [BatteryManager::class, VibratorManager::class, Vibrator::class, VibrationEffect::class, Context::class, PowerManager::class, PowerManager.WakeLock::class])
 @NativeProxyForClasses(namespace = "android::view", classes = [Window::class, WindowManager.LayoutParams::class])
 @NativeProxyForClasses(namespace = "android::media", classes = [AudioManager::class])
 @NativeProxyForClasses(namespace = "android::drawable", classes = [android.R.drawable::class])
 @NativeProxyForClasses(namespace = "android::app", classes = [Activity::class, Notification::class, Notification.Builder::class, NotificationChannel::class, NotificationManager::class])
 @NativeProxyForClasses(namespace = "android::provider", classes = [Settings.Global::class, Settings.System::class, Settings::class])
 @NativeProxyForClasses(namespace = "android::content", classes = [Intent::class])

The annotation processor then generates the C++ headers in the qtjenny_output directory. Those C++ headers contain the needed JNI boilerplate code to access the Android APIs used in this demo.

The app level build.gradle script in qtjenny_generator specifies the arguments for kapt which QtJenny implements. These arguments are parsed in the QtJenny compiler and used in the generation process.

 kapt {
     arguments {
         // pass arguments to jenny
         arg("jenny.outputDirectory", project.file("../../qtjenny_output"))
         arg("jenny.templateDirectory", project.file("../templates"))
         arg("jenny.headerOnlyProxy", "true")
         arg("jenny.useJniHelper", "false")
         arg("jenny.useTemplates", "true")
     }
 }
Using the generated C++ headers in the Qt Quick application

The qtjenny_consumer is the Qt Quick Application that uses the C++ headers generated by the qtjenny_generator. We include the generated headers in backend.h file and use them in backend.cpp to access various Android APIs.

The app's UI consists of one Main.qml file, which contain the following controls and actions.

Wake locks

You can activate and deactivate either full or partial wake locks, using Switches. When checked they invoke a function in backend.cpp which activates the wake lock and set the wake lock status text.

 if (checked) {
     myBackEnd.setFullWakeLock()
     if (partialWakeLock.checked)
         partialWakeLock.click()
     mainWindow.wakeLockStatus = "Full WakeLock active"

Setting partial wake lock uses the WakeLockProxy class that connects to PowerManager.WakeLock Android API. Setting full wake lock is done by using WindowProxy that connects to Window Android API.

Vibrate

You can trigger a vibration using the vibrate function in backend.cpp, that uses VibrationEffectProxy, VibratorManagerProxy and VibratorProxy classes to perform the vibration.

Notifications

Sending a notification is handled in the notify function in backend.cpp, with use of the NotificationManagerProxy class.

The notification is already created during the Backend class initialization in createNotification function.

Adjusting the brightness

Adjsting brightness is handled in the adjustBrightness function in backend.cpp, with the use of IntentProxy, SettingsProxy, SystemProxy, ContextProxy, LayoutParamsProxy and WindowProxy classes.

Adjusting the volume

Adjusting volume is handled in the adjustVolume function in backend.cpp, with the use of GlobalProxy and AudioManagerProxy classes.

Example project @ code.qt.io