import java.util.Properties plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("rust") } val tauriProperties = Properties().apply { val propFile = file("tauri.properties") if (propFile.exists()) { propFile.inputStream().use { load(it) } } } // Release signing: loaded from gen/android/keystore.properties if present. // Falls back to no signing config (debug-signed) when the file is absent. val keystoreProperties = Properties().apply { val propFile = rootProject.file("keystore.properties") if (propFile.exists()) { propFile.inputStream().use { load(it) } } } // Side-by-side release: set by `scripts/build-android.sh release --debug`, which // exports JT_SIDE_BY_SIDE=1. It puts a fully R8-minified release build into the // debug applicationId slot, signed with the local debug keystore — so you can // test what minification actually produces (R8 stripping JNI-loaded classes has // broken release APKs here before) without the real signing key and without // uninstalling your working install. Unset in CI, so distributable release // builds are untouched. val sideBySideRelease = System.getenv("JT_SIDE_BY_SIDE").let { it == "1" || it == "true" } android { compileSdk = 36 namespace = "com.dtourolle.jellytau" defaultConfig { manifestPlaceholders["usesCleartextTraffic"] = "false" // Launcher/app names come from placeholders so the debug build can // rename itself without touching the generated strings.xml (a // resValue() override there would collide with Tauri's own entries). manifestPlaceholders["appLabel"] = "@string/app_name" manifestPlaceholders["activityLabel"] = "@string/main_activity_title" applicationId = "com.dtourolle.jellytau" minSdk = 24 targetSdk = 36 versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt() versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0") } signingConfigs { create("release") { keystoreProperties.getProperty("storeFile")?.let { storeFile = file(it) storePassword = keystoreProperties.getProperty("storePassword") keyAlias = keystoreProperties.getProperty("keyAlias") keyPassword = keystoreProperties.getProperty("keyPassword") } } } buildTypes { getByName("debug") { // Distinct applicationId so the debug build installs SIDE BY SIDE // with a release/store install instead of demanding an uninstall // (different signing keys on the same package = INSTALL_FAILED_ // UPDATE_INCOMPATIBLE). It gets its own data dir, its own settings // and its own offline cache — the two are fully independent apps. // // This changes only the *application* id. The Kotlin/JNI classes // stay in the `namespace` package (com.dtourolle.jellytau), so the // fully-qualified class names Rust looks up over JNI, the manifest // entry and the R8 keep rules are all unaffected. The // FileProvider authority is already ${applicationId}-relative. applicationIdSuffix = ".debug" versionNameSuffix = "-debug" manifestPlaceholders["appLabel"] = "JellyTau Debug" manifestPlaceholders["activityLabel"] = "JellyTau Debug" manifestPlaceholders["usesCleartextTraffic"] = "true" isDebuggable = true isJniDebuggable = true isMinifyEnabled = false packaging { jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so") jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so") jniLibs.keepDebugSymbols.add("*/x86/*.so") jniLibs.keepDebugSymbols.add("*/x86_64/*.so") } } getByName("release") { if (sideBySideRelease) { // Same slot, name and version scheme as the debug build type, // plus "-release" so you can tell from Settings > Apps which of // the two is currently sitting there. Signed with the debug // keystore: it shares a signature with the debug build, so the // two replace each other cleanly instead of colliding. applicationIdSuffix = ".debug" versionNameSuffix = "-debug-release" manifestPlaceholders["appLabel"] = "JellyTau Debug" manifestPlaceholders["activityLabel"] = "JellyTau Debug" signingConfig = signingConfigs.getByName("debug") } else if (keystoreProperties.getProperty("storeFile") != null) { signingConfig = signingConfigs.getByName("release") } isMinifyEnabled = true proguardFiles( *fileTree(".") { include("**/*.pro") } .plus(getDefaultProguardFile("proguard-android-optimize.txt")) .toList().toTypedArray() ) } } // Java 17 bytecode. AGP 8.11 already requires a JDK 17 toolchain to run // (the builder image ships openjdk-17), so "1.8" was only capping the // bytecode we emit, not the JDK in use. Kotlin's jvmTarget and javac's // source/targetCompatibility must agree or AGP 8 fails the build, so all // three move together. compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { jvmTarget = "17" } buildFeatures { buildConfig = true } } rust { rootDirRel = "../../../" } dependencies { implementation("androidx.webkit:webkit:1.14.0") implementation("androidx.appcompat:appcompat:1.7.1") implementation("androidx.activity:activity-ktx:1.10.1") implementation("com.google.android.material:material:1.12.0") // Media3 dependencies for audio playback implementation("androidx.media3:media3-exoplayer:1.5.0") implementation("androidx.media3:media3-exoplayer-hls:1.5.0") implementation("androidx.media3:media3-session:1.5.0") implementation("androidx.media3:media3-common:1.5.0") implementation("com.google.guava:guava:33.0.0-android") // Media library for VolumeProviderCompat (remote volume control) implementation("androidx.media:media:1.7.0") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.4") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") } apply(from = "tauri.build.gradle.kts")