Testing a debug build meant uninstalling the real one first: same
applicationId signed with a different key is INSTALL_FAILED_UPDATE_
INCOMPATIBLE, so every experiment cost the app's settings, credentials
and offline cache.
The debug build type now carries applicationIdSuffix ".debug" and
versionNameSuffix "-debug", so it installs as com.dtourolle.jellytau.debug
("JellyTau Debug", 0.5.5-debug) with its own data directory — two
independent apps on one device.
Only the *application* id is suffixed. Kotlin classes stay in the
`namespace` package com.dtourolle.jellytau, so the JNI loadClass lookups
in player/android/mod.rs, the manifest <service> entry and the R8 keep
rules are untouched, and the FileProvider authority was already
${applicationId}-relative. Launcher names come from the appLabel /
activityLabel manifestPlaceholders rather than resValue, which would
collide with Tauri's generated strings.xml; release resolves them back to
@string/app_name and merges byte-identical.
deploy-android.sh reports the target package and explains an
UPDATE_INCOMPATIBLE failure instead of leaving it raw; logcat.sh takes a
debug|release argument (it was filtering on com.jellytau.app, a package
that has never existed) and attaches by pid when the app is running.
Verified: aapt2 badging on the built APK reports
com.dtourolle.jellytau.debug / 0.5.5-debug / "JellyTau Debug", and the
release manifest merge is unchanged.
123 lines
4.8 KiB
Kotlin
123 lines
4.8 KiB
Kotlin
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) }
|
|
}
|
|
}
|
|
|
|
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
|
|
// <service> 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 (keystoreProperties.getProperty("storeFile") != null) {
|
|
signingConfig = signingConfigs.getByName("release")
|
|
}
|
|
isMinifyEnabled = true
|
|
proguardFiles(
|
|
*fileTree(".") { include("**/*.pro") }
|
|
.plus(getDefaultProguardFile("proguard-android-optimize.txt"))
|
|
.toList().toTypedArray()
|
|
)
|
|
}
|
|
}
|
|
kotlinOptions {
|
|
jvmTarget = "1.8"
|
|
}
|
|
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") |