QtJenny: Generating C++ proxy classes to access Android APIs | Qt 6.10
Description: Demo showcasing the use of QtJenny.

- Qt 6.10
- QtJenny: Generating C++ proxy classes to access Android APIs
- Qt 6.10.3 Reference Documentation

# 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 UI application of this 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 distinctive parts, one is the UI application i.e the Qt Quick project called `qtjenny_consumer` that uses the code generated by QtJenny, and the other one is a combination of three different Gradle projects called `qtjenny_general`, `qtjenny_callback` and `qtjenny_baseclass` that together handle the code generation using QtJenny. Each of the three gradle projects contain class annonations and `Gradle` configurations for QtJenny code generation.

The QtJenny code generation is triggered automatically during the CMake configuration of `qtjenny_consumer` project by executing `kaptReleaseKotlin` gradle task in each of `qtjenny_general`, `qtjenny_callback` and `qtjenny_baseclass` gradle projects.

     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_baseclass")
         execute_process(COMMAND ${gradlew_cmd} ${gradlew_arg} ${gradlew_task}
         WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/qtjenny_general")
         execute_process(COMMAND ${gradlew_cmd} ${gradlew_arg} ${gradlew_task}
         WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/qtjenny_callback")
     else()
         message(FATAL_ERROR "Example only works on Android")
     endif()

Running the `qtjenny_consumer` project launches the UI application.

#### Generating the C++ headers

Running the `kaptReleaseKotlin` task in the gradle projects triggers an annotation processor that processes the annotations declared in `GenerateCppCode.kt`.

Code snippet from the `GenerateCppCode.kt` file of `qtjenny_general` :

     @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,
         Handler::class, Looper::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, ContentResolver::class])

In the case of `qtjenny_general` the annotation processor generates the C++ headers to `qtjenny_output` directory. Those C++ headers contain the needed JNI boilerplate code to access the Android APIs used in the `qtjenny_consumer`.

The app level `build.gradle` script in `qtjenny_general` 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")
         }
     }

##### qtjenny_general

The `qtjenny_general` takes care of generating wrappers for Java classes, to allow constructing Java objects and invoking methods on them. These wrappers wrap the usual method calls, and for callbacks, the qtjenny_baseclass and qtjenny_callback are used.

##### qtjenny_baseclass

The `qtjenny_baseclass` effectively allows extending the Java class ContentObserver in C++ code, using a combination of the baseclass generation and callback generation.

##### qtjenny_callback

The `qtjenny_callback` effectively allows implementing a Java interface in C++. The `qtjenny_baseclass` class extending also uses this mechanism.

For implementing an interface, a Java Proxy class is constructed, using an InvocationHandler that performs virtual function calls into the generated C++ class hierachy.

For extending a base class, the generated extended class performs virtual function calls into the generated C++ class hierarchy.

#### 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_general`. 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 is connected to the `BackEnd` class using Q_INVOKABLE and Q_PROPERTY macros.

The UI contains the following controls and actions.

##### Wake locks

You can activate and deactivate either full or partial wake locks, using Switches. These switches trigger corresponding `Q_INVOKABLE` functions of `Backend` class, that activate the wake lock and set the wake lock status text that will be shown to user in the UI.

     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 `BackEnd::vibrate()` function, that uses `VibrationEffectProxy`, `VibratorManagerProxy` and `VibratorProxy` classes to create the vibration.

##### Notifications

Sending a notification is handled in the `BackEnd::notfy()` function in `Backend` with use of the `NotificationManagerProxy` class.

The notification is already created during the `Backend` class initialization in `BackEnd::createNotification()` function.

##### Adjusting the brightness

Adjusting brightness with the brightness slider in the UI triggers property change in `Backend` class that will be handled in `BackEnd::setBrightness()` function. There the system brightness is adjusted with the use of `SettingsProxy`, `SystemProxy`, `ContextProxy`, `LayoutParamsProxy` and `WindowProxy` classes.

The brightness slider is dynamically synchronized with the system brightness, so if the system brightness is changed from outside the application the brightness slider adjusts to reflect the current system brightness.

If the application does not have permission to write system settings, an `Activity` is started with the `ACTION_MANAGE_WRITE_SETTINGS` intent. After this `Activity` is started, user has to manually give permission for the application to write system settings.

##### Adjusting the volume

Adjusting volume with volume slider in the UI triggers property change in `Backend` class that will be handled in `BackEnd::setVolume()` function. There the system volume is adjusted with the use of `AudioManagerProxy` class.

The volume slider is dynamically synchronized with the system music volume, so if the system music volume is changed from outside the application the volume slider adjusts to reflect the current system music volume.

Example project @ code.qt.io