# ⚠️ 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)** ```bash ./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)** ```bash ./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. ```bash ./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 `` 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` produces installable APKs that are **not releases**. Two ways in: | Trigger | Result | |---------|--------| | **push to `master`** | Refreshes the rolling **`latest`** pre-release automatically | | **`workflow_dispatch`** | Builds any branch on demand; optionally publishes it as `test-` | #### The rolling `latest` build Every push to `master` (bar doc-only ones) rebuilds and replaces the APK on the `latest` pre-release. Both the tag and the asset name are stable, so the download URL never changes: ``` https://gitea.tourolle.paris/dtourolle/jellytau/releases/download/latest/jellytau-latest.apk ``` Send that link to a tester once and it keeps serving the current build. No account needed — release assets are public, unlike Actions artifacts. #### What you get, and why it is safe Both variants install into the `com.dtourolle.jellytau.debug` slot: | Variant | What it is | When | |---------|-----------|------| | `side-by-side-release` (default, and what `latest` always is) | 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 | Three properties make an automatic build on every master push safe: - **It cannot replace a real install.** The applicationId is suffixed `.debug`, so it sits beside the store build with its own data. A broken master commit can never take out somebody's working app. - **The production signing key is not involved.** That stays in the tag-driven `build-release.yml`. This workflow needs no secrets beyond the API token. - **The tag is `latest`/`test-*`, never `v*`.** Only `v*` triggers `build-release.yml`. And the desktop updater reads a static `latest.json` from the `updater` branch rather than the release list, so nothing here is offered to existing users. **Known gap:** the APK builds in parallel with `build-and-test.yml`, not after it, so `latest` can carry a commit whose tests later fail. Cross-workflow dependencies are not reliably available here, and duplicating the test job would double an already hour-long queue on a single-slot runner. Check the commit's CI status before handing the link to somebody. Runs are serialised and `cancel-in-progress` is on, so a burst of pushes to master collapses into one build rather than one per commit. ### 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!