Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
f3a0785
Modernize Android Gradle and CI workflow
cagnulein May 3, 2026
ff74bc6
Align Android CI to Qt 5.15.0 and update Garmin SDK
cagnulein May 4, 2026
fc13f67
Isolate Android CI Gradle user home
cagnulein May 4, 2026
2b3d51b
Harden Android CI Gradle wrapper invocation
cagnulein May 4, 2026
d48ca67
Force Android CI wrapper JVM gradle home
cagnulein May 4, 2026
655b5fa
Bypass wrapper in Android CI packaging
cagnulein May 4, 2026
59c39f1
Add Android CI residual property guard
cagnulein May 4, 2026
11c7b62
Use aux-mode for Android CI project generation
cagnulein May 4, 2026
afbae52
Preseed libs.xml for Android CI aux-mode
cagnulein May 4, 2026
66fbdbd
Seed Android package tree for CI aux-mode
cagnulein May 4, 2026
f8309a9
Set qt5AndroidDir in Android CI Gradle project
cagnulein May 4, 2026
1b37c7c
Seed Ministro package into Android CI project
cagnulein May 4, 2026
b35f546
Enable AIDL explicitly for Android Gradle build
cagnulein May 4, 2026
c513b0f
Healthfit test
cagnulein Jun 4, 2026
7a49247
Merge branch 'master' into android-gradle-modernization
cagnulein Jun 4, 2026
e442964
fix
cagnulein Jun 4, 2026
3da872a
Merge branch 'android-gradle-modernization' into healthfit-override
cagnulein Jun 4, 2026
1dfcfe4
android from 34 to 35
cagnulein Jun 4, 2026
038b277
let's try
cagnulein Jun 5, 2026
c4896ba
request permissions added
cagnulein Jun 5, 2026
48573f6
git ignore updated
cagnulein Jun 5, 2026
605e1e0
Update HealthConnectHelper.kt
cagnulein Jun 5, 2026
996fd4d
logs
cagnulein Jun 5, 2026
8932d3f
permissions
cagnulein Jun 5, 2026
eb38d8a
permissions
cagnulein Jun 5, 2026
f3121b0
Update HealthConnectHelper.kt
cagnulein Jun 5, 2026
2194061
Update HealthConnectPermissionActivity.kt
cagnulein Jun 5, 2026
f95e72f
Fix Health Connect permission rationale intent
cagnulein Jun 5, 2026
881ec9e
Write Health Connect sessions as activities
cagnulein Jun 5, 2026
47e3c85
git ignore updated
cagnulein Jun 5, 2026
6f49808
Android Health Connect: fix missing activity on stop, add power/caden…
cagnulein Jun 8, 2026
e7615b3
fix: remove duplicate entries in build.gradle and AndroidManifest.xml…
cagnulein Jun 8, 2026
f192714
feat: ask Health Connect permissions at every startup, write with par…
cagnulein Jun 8, 2026
2651086
Merge branch 'master' into healthfit-override
cagnulein Jun 8, 2026
c73ee32
Merge 26510861dfebca505e94d62bdccb5fea574027e6 into 4bb9d55f04175acdd…
cagnulein Jun 8, 2026
f510145
chore: update translation strings [automated]
github-actions[bot] Jun 8, 2026
014bc4c
Merge branch 'healthfit-override' of https://github.com/cagnulein/qdo…
cagnulein Jun 8, 2026
fe016ec
feat(health-connect): add elevation, steps, HRV and treadmill cadence…
cagnulein Jun 8, 2026
7042416
Merge fe016ec6dbc2c2e39bc22afd5a643ffe68b28f64 into 0c9bbf663f5ce44cb…
cagnulein Jun 8, 2026
88fb757
chore: update translation strings [automated]
github-actions[bot] Jun 8, 2026
7e7c6ff
fix(health-connect): show app name instead of "Manually logged"
cagnulein Jun 8, 2026
c428b06
Merge branch 'master' into healthfit-override
cagnulein Jun 9, 2026
e8733c9
Delete docs/android-gradle-modernization-log.md
cagnulein Jun 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 127 additions & 29 deletions .github/workflows/main.yml

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ src/build/*
src/debug-*

src/secret.h
src/secrets.h
secrets.h

*.swo
*.swp
Expand Down Expand Up @@ -48,11 +50,15 @@ google_test/*
*build-*
!build-qdomyos-zwift-Qt_*_for_iOS-Debug # Needed for Apple Watch
src/inner_templates/googlemaps/cesium-key.js
src/inner_templates/googlemaps/cesium.js
cesium.js
*.autosave
.vscode/settings.json
/tst/Devices/.vs
src/inner_templates/googlemaps/cesium-key.js
src/qdomyos-zwift.pro.user.49de507
/.vs
src/android/.gradle/
src/android/local.properties
src/translations/*.qm
/tools/__pycache__
36 changes: 35 additions & 1 deletion src/android/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
Remove the comment if you do not require these default features. -->
<!-- %%INSERT_FEATURES -->

<queries>
<package android:name="com.google.android.apps.healthdata" />
</queries>

<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<application android:hardwareAccelerated="true" android:debuggable="false" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="qdomyos-zwift" android:extractNativeLibs="true" android:icon="@drawable/icon" android:usesCleartextTraffic="true">
<activity android:theme="@style/Theme.AppCompat" android:exported="true" android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.cagnulen.qdomyoszwift.CustomQtActivity" android:label="QZ" android:launchMode="singleTop">
Expand Down Expand Up @@ -120,6 +124,25 @@
android:value="ocr" />

<activity android:name="org.cagnulen.qdomyoszwift.MyActivity" />
<activity
android:name=".HealthConnectPermissionActivity"
android:exported="false"
android:theme="@style/Theme.AppCompat">
<intent-filter>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>
</activity>

<activity-alias
android:name="ViewPermissionUsageActivity"
android:exported="true"
android:targetActivity=".HealthConnectPermissionActivity"
android:permission="android.permission.START_VIEW_PERMISSION_USAGE">
<intent-filter>
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
<category android:name="android.intent.category.HEALTH_PERMISSIONS" />
</intent-filter>
</activity-alias>

<receiver android:name=".MediaButtonReceiver" android:exported="true" android:permission="android.permission.MODIFY_AUDIO_SETTINGS">
<intent-filter>
Expand All @@ -129,7 +152,7 @@
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
</application>

<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="36" tools:overrideLibrary="com.google.android.play.agesignals,com.google.android.gms.common" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="36" tools:overrideLibrary="com.google.android.play.agesignals,com.google.android.gms.common,androidx.health.connect.client" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>
Expand All @@ -149,4 +172,15 @@
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.health.WRITE_EXERCISE" />
<uses-permission android:name="android.permission.health.WRITE_DISTANCE" />
<uses-permission android:name="android.permission.health.WRITE_TOTAL_CALORIES_BURNED" />
<uses-permission android:name="android.permission.health.WRITE_HEART_RATE" />
<uses-permission android:name="android.permission.health.WRITE_POWER" />
<uses-permission android:name="android.permission.health.WRITE_SPEED" />
<uses-permission android:name="android.permission.health.WRITE_CYCLING_PEDALING_CADENCE" />
<uses-permission android:name="android.permission.health.WRITE_ELEVATION_GAINED" />
<uses-permission android:name="android.permission.health.WRITE_STEPS" />
<uses-permission android:name="android.permission.health.WRITE_STEPS_CADENCE" />
<uses-permission android:name="android.permission.health.WRITE_HEART_RATE_VARIABILITY" />
</manifest>
115 changes: 75 additions & 40 deletions src/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,33 +1,44 @@
buildscript {
repositories {
google()
jcenter()
mavenCentral()
gradlePluginPortal()
}

dependencies {
classpath 'com.android.tools.build:gradle:3.6.0'
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.4'
classpath 'com.android.tools.build:gradle:8.13.2'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.20'
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.5'
}
}

plugins {
id "com.google.protobuf" version "0.9.4"
}

repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
mavenCentral()
maven { url = 'https://jitpack.io' }
}

apply plugin: 'com.android.application'
apply plugin: 'org.jetbrains.kotlin.android'
apply plugin: 'com.google.protobuf'

def amazon = System.getenv('AMAZON')
def androidCompileSdk = (project.findProperty('androidCompileSdkVersion') ?: '35').toString()
def qtAndroidDir = project.findProperty('qt5AndroidDir') ?: System.getenv('QT_ANDROID_DIR')
println(amazon)
def qtAndroidBaseDir = qtAndroidDir ? qtAndroidDir.toString().replaceAll('/src/android/java/?$', '') : null
def qtAndroidJavaRootDir = qtAndroidDir?.toString()?.endsWith('/src/android/java') ? qtAndroidDir.toString() : (qtAndroidBaseDir ? qtAndroidBaseDir + '/src/android/java' : null)
def qtAndroidJarDir = qtAndroidBaseDir ? qtAndroidBaseDir + '/jar' : null
def qtAndroidJavaSrcDir = qtAndroidJavaRootDir ? qtAndroidJavaRootDir + '/src' : 'qt-android-missing/src/android/java/src'
def qtAndroidJavaResDir = qtAndroidJavaRootDir ? qtAndroidJavaRootDir + '/res' : 'qt-android-missing/src/android/java/res'

dependencies {
if (qtAndroidJarDir) {
implementation fileTree(dir: qtAndroidJarDir, include: ['*.jar'])
}

implementation "androidx.core:core:1.12.0"
implementation "androidx.activity:activity-ktx:1.9.3"
implementation "androidx.core:core-ktx:1.12.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0"
implementation 'com.google.protobuf:protobuf-javalite:3.25.1'
Expand All @@ -39,24 +50,27 @@ dependencies {
// google play store
implementation 'com.google.android.gms:play-services-mlkit-text-recognition:18.0.2'
implementation 'com.google.android.play:age-signals:0.0.3'
implementation 'androidx.health.connect:connect-client:1.1.0-alpha11'
}

def appcompat_version = "1.3.1"
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation "com.android.billingclient:billing:8.0.0"
implementation 'com.android.support:appcompat-v7:28.0.0'

implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation files('libs/usb-serial-for-android-3.8.1.aar')
androidTestImplementation "com.android.support:support-annotations:28.0.0"
implementation 'com.google.android.gms:play-services-wearable:19.0.0'
implementation "com.android.billingclient:billing:8.0.0"
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation files('libs/usb-serial-for-android-3.8.1.aar')
implementation 'com.google.android.gms:play-services-wearable:19.0.0'

implementation 'com.jakewharton.timber:timber:5.0.1'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.60'
implementation 'org.bouncycastle:bcprov-jdk15on:1.60'
implementation("com.garmin.connectiq:ciq-companion-app-sdk:2.2.0@aar")
implementation("com.garmin.connectiq:ciq-companion-app-sdk:2.3.0@aar")

testImplementation 'junit:junit:4.13.2'
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit:2.1.20'
testImplementation 'androidx.health.connect:connect-client:1.1.0-alpha11'
testImplementation 'org.json:json:20231013'
}

import org.apache.tools.ant.taskdefs.condition.Os
Expand Down Expand Up @@ -92,52 +106,73 @@ android {
* updated by QtCreator and androiddeployqt tools.
* Changing them manually might break the compilation!
*******************************************************/

compileSdkVersion androidCompileSdkVersion.toInteger()

buildToolsVersion '29.0.2'
namespace = 'org.cagnulen.qdomyoszwift'
compileSdk androidCompileSdk.toInteger()

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

kotlinOptions {
jvmTarget = '1.8'
}

sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qt5AndroidDir + '/res', 'res']
java.srcDirs = [qtAndroidJavaSrcDir, 'src', 'java']
kotlin.srcDirs = ['src', 'java']
aidl.srcDirs = [qtAndroidJavaSrcDir, 'src', 'aidl']
res.srcDirs = [qtAndroidJavaResDir, 'res']
resources.srcDirs = ['resources']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
debug {
java.srcDirs += ["$buildDir/generated/aidl_source_output_dir/debug/out"]
}
release {
java.srcDirs += ["$buildDir/generated/aidl_source_output_dir/release/out"]
}
test {
java.srcDirs = ['tests']
kotlin.srcDirs = ['tests']
}
}

lint {
abortOnError = false
checkReleaseBuilds = false
}

lintOptions {
abortOnError false
checkReleaseBuilds false
buildFeatures {
aidl = true
}

// Do not compress Qt binary resources file
aaptOptions {
noCompress 'rcc'
androidResources {
noCompress += 'rcc'
}

defaultConfig {
applicationId = "org.cagnulen.qdomyoszwift"
resConfig "en"
compileSdkVersion 33
minSdkVersion = 21
targetSdkVersion = 36
minSdk 21
targetSdk 36
}

tasks.all { task ->
if (task.name == 'compileDebugJavaWithJavac' && amazon == "1") {
task.dependsOn copyArm64Directory
task.dependsOn copyArm32Directory
tasks.all { task ->
if (task.name == 'compileDebugJavaWithJavac' && amazon == "1") {
task.dependsOn copyArm64Directory
task.dependsOn copyArm32Directory
}
}
}

if (!qtAndroidDir) {
throw new GradleException("qt5AndroidDir is not set. Install Qt for Android and set qt5AndroidDir in gradle.properties or via the QT_ANDROID_DIR environment variable.")
}
}


Expand Down
5 changes: 5 additions & 0 deletions src/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
android.useAndroidX=true
android.enableJetifier=true
org.gradle.jvmargs=-Xmx4096m
systemProp.file.encoding=UTF-8
org.gradle.warning.mode=all
androidCompileSdkVersion=35
android.suppressUnsupportedCompileSdk=35
protoc_platform=osx-x86_64
2 changes: 1 addition & 1 deletion src/android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
12 changes: 9 additions & 3 deletions src/android/src/ChannelService.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package org.cagnulen.qdomyoszwift;

import android.content.pm.ApplicationInfo;

import com.dsi.ant.AntService;
import com.dsi.ant.channel.*;
import com.dsi.ant.message.ChannelId;
Expand Down Expand Up @@ -60,6 +62,10 @@ public class ChannelService extends Service {
BikeChannelController bikeChannelController = null; // Added BikeChannelController reference
BikeTransmitterController bikeTransmitterController = null; // Added BikeTransmitterController reference

private boolean isDebuggableBuild() {
return (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}

private ServiceConnection mAntRadioServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Expand Down Expand Up @@ -494,7 +500,7 @@ public void onReceive(Context context, Intent intent) {
};

private void doBindAntRadioService() {
if (BuildConfig.DEBUG) QLog.v(TAG, "doBindAntRadioService");
if (isDebuggableBuild()) QLog.v(TAG, "doBindAntRadioService");

ContextCompat.registerReceiver(
this,
Expand All @@ -509,13 +515,13 @@ private void doBindAntRadioService() {
}

private void doUnbindAntRadioService() {
if (BuildConfig.DEBUG) QLog.v(TAG, "doUnbindAntRadioService");
if (isDebuggableBuild()) QLog.v(TAG, "doUnbindAntRadioService");

// Stop listing for channel available intents
try {
unregisterReceiver(mChannelProviderStateChangedReceiver);
} catch (IllegalArgumentException exception) {
if (BuildConfig.DEBUG)
if (isDebuggableBuild())
QLog.d(TAG, "Attempting to unregister a never registered Channel Provider State Changed receiver.");
}

Expand Down
1 change: 1 addition & 0 deletions src/android/src/CustomQtActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate: CustomQtActivity initialized");
dispatchOAuthCallback(getIntent());
AgeSignalsHelper.requestAgeSignals(this);
HealthConnectHelper.initialize(this);

// This tells the OS that we want to handle the display cutout area ourselves
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Expand Down
Loading
Loading