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.