EARHORNMUSIC UPnP · Android TV · Phones · Android Auto · GNU/Linux
Release 0.5.0

build.gradle.kts

Each line, with the release that last changed it.

0.5.01import java.util.Properties
2
3plugins {
4 alias(libs.plugins.android.application)
5 alias(libs.plugins.kotlin.android)
6 alias(libs.plugins.kotlin.compose)
7 alias(libs.plugins.kotlin.serialization)
8 alias(libs.plugins.ksp)
9 alias(libs.plugins.hilt)
10}
11
12// Private, per-machine values, read from earhorn-private.properties next to this
13// file. That file is for your own builds only: never commit or publish it.
14// Public builds: run Gradle with -Pearhorn.public, which ignores the file and
15// leaves both values empty (those two features are then off). Keys:
16// earhorn.musicnowStatusUrl=http://<host>:8642/status
17// earhorn.domEthaDefaultUrl=http://<host>/mediacontrol.html
18val earhornLocal = Properties().also { props ->
19 if (!project.hasProperty("earhorn.public")) {
20 project.file("earhorn-private.properties").takeIf { it.isFile }?.inputStream()?.use { props.load(it) }
21 }
22}
23fun earhornLocalString(key: String): String =
24 "\"" + (earhornLocal.getProperty(key) ?: "").trim().replace("\\", "\\\\").replace("\"", "\\\"") + "\""
25
26android {
27 namespace = "dev.stave"
28 compileSdk = libs.versions.compileSdk.get().toInt()
29 installation {
30 // The kukui Chromebase (MT8183, network ADB) can take minutes to
31 // complete an install — the ddmlib default gives up long before
32 // the ChromeOS ARC container finishes. 10 minutes of headroom.
33 timeOutInMs = 600_000
34 // -r reinstall keeping data, -d allow version downgrade while
35 // iterating between zips/builds on multiple test devices.
36 installOptions("-r", "-d")
37 }
38 defaultConfig {
39 applicationId = "dev.stave"
40 minSdk = libs.versions.minSdk.get().toInt()
41 targetSdk = libs.versions.targetSdk.get().toInt()
42 // 5 = the deployed 0.2.0 build. Never reuse a versionCode: devices
43 // refuse an update that does not increase it.
44 versionCode = 51
45 versionName = "0.5.0"
46 vectorDrawables { useSupportLibrary = true }
47 buildConfigField("String", "MUSICNOW_STATUS_URL", earhornLocalString("earhorn.musicnowStatusUrl"))
48 buildConfigField("String", "DOM_ETHA_DEFAULT_URL", earhornLocalString("earhorn.domEthaDefaultUrl"))
49 }
50
51 flavorDimensions += "distribution"
52 productFlavors {
53 create("play") {
54 dimension = "distribution"
55 // With Google Cast. Shipped to Play Store for devices with GPS.
56 }
57 create("libre") {
58 dimension = "distribution"
59 applicationIdSuffix = ".libre"
60 versionNameSuffix = "-libre"
61 // No Cast. For Fire TV, GrapheneOS, F-Droid.
62 }
63 }
64
65 buildTypes {
66 debug {
67 isMinifyEnabled = false
68 }
69 release {
70 isMinifyEnabled = true
71 // Resource shrinking is OFF — even with res/raw/keep.xml listing the
72 // launcher icons, AGP's resource shrinker mangles manifest-only icon
73 // references in release builds, replacing the launcher PNG with a
74 // surviving drawable (in our case, empty_clef → APK shows clef
75 // icon instead of the EARHORN badge). Code shrinking via R8 (minify)
76 // remains on for size; only the resource pass is disabled.
77 isShrinkResources = false
78 // Sign release builds with the debug keystore so Android Studio's
79 // run/install flow works for local testing. Without this, the
80 // release APK comes out as `app-play-release-unsigned.apk` and
81 // can't be installed — the installer fails (sometimes surfacing
82 // as INSTALL_BASELINE_PROFILE_FAILED because the install never
83 // landed and the post-install baseline-profile step then can't
84 // find the package).
85 //
86 // Production / Play Store releases SHOULD override this with a
87 // real release keystore via an environment-driven config — but
88 // until we set that up, the debug key keeps local iteration
89 // unblocked. The debug keystore is auto-generated by Android
90 // Studio at ~/.android/debug.keystore on first build.
91 signingConfig = signingConfigs.getByName("debug")
92 proguardFiles(
93 getDefaultProguardFile("proguard-android-optimize.txt"),
94 "proguard-rules.pro"
95 )
96 }
97 }
98
99 compileOptions {
100 sourceCompatibility = JavaVersion.VERSION_17
101 targetCompatibility = JavaVersion.VERSION_17
102 }
103
104 kotlinOptions {
105 jvmTarget = "17"
106 freeCompilerArgs += listOf("-opt-in=kotlin.RequiresOptIn")
107 }
108
109 buildFeatures {
110 compose = true
111 // For the two private values above (see earhorn-private.properties).
112 buildConfig = true
113 }
114
115 packaging {
116 resources {
117 excludes += setOf(
118 "/META-INF/{AL2.0,LGPL2.1}",
119 "/META-INF/DEPENDENCIES",
120 "/META-INF/LICENSE*",
121 "/META-INF/NOTICE*",
122 "/META-INF/*.kotlin_module"
123 )
124 pickFirsts += setOf(
125 "/META-INF/INDEX.LIST"
126 )
127 }
128 }
129}
130
131dependencies {
132 // AndroidX core
133 implementation(libs.androidx.core.ktx)
134 implementation(libs.androidx.appcompat)
135 implementation(libs.androidx.lifecycle.runtime.ktx)
136 implementation(libs.androidx.lifecycle.viewmodel.compose)
137 implementation(libs.androidx.activity.compose)
138
139 // Compose BOM governs versions of all compose libs below
140 implementation(platform(libs.compose.bom))
141 implementation(libs.compose.ui)
142 implementation(libs.compose.ui.graphics)
143 implementation(libs.compose.ui.tooling.preview)
144 debugImplementation(libs.compose.ui.tooling)
145 implementation(libs.compose.material3)
146 implementation(libs.compose.material.icons.extended)
147
148 // Compose for TV
149 implementation(libs.androidx.tv.foundation)
150 implementation(libs.androidx.tv.material)
151
152 // Navigation + Hilt
153 implementation(libs.androidx.navigation.compose)
154 implementation(libs.androidx.hilt.navigation.compose)
155 implementation(libs.hilt.android)
156 ksp(libs.hilt.compiler)
157
158 // Media3
159 implementation(libs.media3.exoplayer)
160 implementation(libs.media3.session)
161 implementation(libs.media3.ui)
162 implementation(libs.media3.datasource.okhttp)
163
164 // Room (for saved servers + preferences that need relations later)
165 implementation(libs.room.runtime)
166 implementation(libs.room.ktx)
167 ksp(libs.room.compiler)
168
169 // DataStore (metadata display prefs)
170 implementation(libs.datastore.preferences)
171
172 // UPnP — jUPnP 3.0.3 (maintained fork of abandoned Cling). Published to Maven Central.
173 // jupnp.android provides AndroidUpnpServiceImpl + AndroidUpnpServiceConfiguration which wires
174 // up the Jetty-backed transport and Recovering* XML processors jUPnP needs on Android.
175 implementation(libs.jupnp.core)
176 implementation(libs.jupnp.support)
177 implementation(libs.jupnp.android)
178
179 // Jetty 9.4 is jUPnP's required transport on Android. The javax.servlet dep bundled with
180 // jetty-servlet ships an empty package that Android's runtime already partially claims; we
181 // drop it here and let jetty pull the real javax.servlet-api through jetty-server instead.
182 implementation(libs.jetty.server)
183 implementation(libs.jetty.servlet) {
184 exclude(group = "org.eclipse.jetty.orbit", module = "javax.servlet")
185 }
186 implementation(libs.jetty.client)
187
188 // jUPnP logs via SLF4J. jdk14 binding routes to java.util.logging → logcat.
189 implementation(libs.slf4j.api)
190 implementation(libs.slf4j.jdk14)
191
192 // Image loading for album art
193 implementation(libs.coil.compose)
194 implementation(libs.coil.network.okhttp)
195 implementation(libs.coil.gif)
196
197 // HTTP
198 implementation(libs.okhttp)
199
200 // Kotlinx
201 implementation(libs.kotlinx.coroutines.android)
202 implementation(libs.kotlinx.serialization.json)
203
204 // Android Auto (cartemplate module)
205 implementation(libs.androidx.car.app)
206 // Legacy MediaSessionCompat — required by Car App Library
207 // MediaPlaybackManager.registerMediaPlaybackToken(). Media3
208 // MediaLibrarySession.sessionCompatToken returns this type, but
209 // the class itself lives in this support library and isn't
210 // pulled in transitively.
211 implementation(libs.androidx.media)
212
213 // Cast — only for the play flavor
214 "playImplementation"(libs.play.services.cast.framework)
215 // MediaRouter — AppCompat-based chooser dialog used by the Cast sender UI.
216 // Cast SDK normally pulls this transitively but pinning explicitly avoids
217 // version drift and makes the dependency obvious.
218 "playImplementation"(libs.androidx.mediarouter)
219}