Files
jellytau/src-tauri/android/README_ANDROID_BUILD.md
T
dtourolle 10d77f1380
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 29m49s
🏗️ Build and Test JellyTau / Supply Chain (push) Successful in 57s
Publish Documentation / Build & publish docs to gitea-pages (push) Successful in 9m25s
Traceability Validation / Check Requirement Traces (push) Successful in 17s
🏗️ Build and Test JellyTau / Android Compile Check (push) Failing after 51s
ci: publish a test APK as a pre-release for outside testers
Gitea artifacts need an account with read access to download, which makes
them useless for handing a build to someone outside the project -- the
actual reason a test APK gets built in the first place.

An optional publish input attaches the APK to a pre-release instead,
whose assets are a plain public URL on a public repo. No merge to master,
no MR, no version tag, and the tester needs no account.

Safe from a feature branch on two counts. The tag is test-<branch> rather
than v*, and only v* triggers build-release.yml, so nothing else reacts
to it. And it cannot reach existing users: the desktop updater reads a
static latest.json from the updater branch, not the release list.

Re-dispatching the same branch replaces the APK on the existing
pre-release rather than accumulating one release per attempt.
2026-08-30 20:06:40 +02:00

6.4 KiB

⚠️ IMPORTANT: Android Build File Locations

Critical Information for Future Development

DO NOT EDIT FILES IN src-tauri/gen/android/ DIRECTLY!

File Structure

This project has TWO sets of Android source files:

  1. src-tauri/android/ - SOURCE FILES (edit these!)

    • This is the template directory
    • Changes here need to be copied to the generated directory
  2. src-tauri/gen/android/ - GENERATED BUILD DIRECTORY (do not edit directly!)

    • This is where Gradle actually builds the APK
    • Files here may be overwritten during builds

How to Make Changes to Android Code

When you need to modify Android/Kotlin files:

  1. Edit the files in src-tauri/android/src/main/java/

  2. Build using the provided script (which auto-syncs files)

    ./scripts/build-android.sh
    

    The build script automatically runs ./scripts/sync-android-sources.sh which copies:

    • src-tauri/android/src/main/java/com/dtourolle/jellytau/player/ → generated directory
    • src-tauri/android/src/main/java/com/dtourolle/jellytau/security/ → generated directory
  3. Manual sync (if needed)

    ./scripts/sync-android-sources.sh
    

Why This Matters

  • If you only edit src-tauri/gen/android/, your changes will be lost
  • If you only edit src-tauri/android/, your changes won't be in the build
  • You must edit both (or edit source and copy to generated)

Debug and release install side by side

The debug build type sets applicationIdSuffix = ".debug" in app/build.gradle.kts, so a debug build is a genuinely separate Android app:

build applicationId launcher name versionName signed with
release com.dtourolle.jellytau jellytau 0.5.5 real key (.env)
release --debug com.dtourolle.jellytau.debug JellyTau Debug 0.5.5-debug-release debug keystore
debug com.dtourolle.jellytau.debug JellyTau Debug 0.5.5-debug debug keystore

release --debug is the side-by-side release: fully R8-minified, exactly what ships, but packaged into the debug slot and signed with the local debug keystore. It exists because R8 has broken release APKs here before (stripping JNI-loaded player/security classes), and reproducing that previously meant building with the real key and clobbering your working install. It shares the applicationId and signature with the plain debug build, so the two replace each other cleanly; only the versionName suffix tells you which is installed.

./scripts/build-and-deploy.sh release --device --debug   # build + install it

The flag is plumbed through as JT_SIDE_BY_SIDE=1, read by build.gradle.kts. CI never sets it, so distributable release builds are untouched.

That means:

  • No uninstall step. Debug builds are signed with the local auto-generated ~/.android/debug.keystore, release builds with the real key. Two different keys on the same package is INSTALL_FAILED_UPDATE_INCOMPATIBLE; two different packages is just two apps.
  • Each has its own data directory — separate settings, credentials, downloads and offline cache. A debug experiment cannot corrupt the state of the build you actually use. This is not optional and cannot be shared: Android gives each applicationId its own UID and enforces the boundary in the kernel. (sharedUserId is deprecated since API 29 and cannot be added to an already-installed app anyway.) You log in again in the debug app, once.
  • Only the application id changes. Kotlin classes stay in the namespace package com.dtourolle.jellytau, so the JNI class lookups in src-tauri/src/player/android/mod.rs, the manifest <service> entry and the R8 keep rules in proguard-jellytau.pro are all unaffected. The FileProvider authority is ${applicationId}.fileprovider, so it follows the suffix automatically.
  • The launcher labels come from the appLabel / activityLabel manifestPlaceholders (AndroidManifest.xml uses ${appLabel}), not from resValue, which would collide with Tauri's generated strings.xml.

Follow the right log stream with ./scripts/logcat.sh [debug|release] (defaults to debug).

Getting a test APK out of CI

.gitea/workflows/build-test-apk.yml builds one from any branch, on demand — run it from Gitea's Actions tab (workflow_dispatch) against the ref you want. It is not a release: nothing is tagged, published, or signed with the real key.

Two variants, both installing into the com.dtourolle.jellytau.debug slot:

Variant What it is When
side-by-side-release (default) R8-minified, exactly what ships, signed with the debug keystore Almost always — a plain debug build cannot catch R8 stripping JNI-loaded classes, which has broken release APKs here before
debug Unminified When you need readable stack traces

There is deliberately no push trigger: the runner has one slot shared with two other projects, so building on every feature-branch commit would starve them. The APK lands as the jellytau-test-apk artifact (7-day retention), named for the branch and short SHA, with its size and SHA256 in the run summary.

Sending a build to an outside tester

Gitea artifacts require an account with read access to download, so an artifact is no use to someone outside the project. Tick publish on the dispatch and the APK is also attached to a pre-release, whose assets are a plain public URL on a public repo — no account, no MR, no merge to master.

Two things make that safe to do from a feature branch:

  • The tag is test-<branch>, not v*. Only v* triggers build-release.yml, so nothing else reacts to it.
  • It cannot reach existing users. The desktop updater reads a static latest.json from the updater branch, not the release list, so a pre-release published this way is invisible to anyone without the link.

Re-dispatching for the same branch replaces the APK on the existing pre-release rather than piling up one release per attempt. Delete the release when testing is over.

Key Files

Player-related Kotlin files:

  • player/JellyTauPlayer.kt - Main player implementation
  • player/JellyTauPlaybackService.kt - MediaSession service for lockscreen controls
  • security/SecureStorage.kt - Android Keystore integration for secure credential storage

Always check BOTH locations exist and match after making changes!