diff --git a/.gitignore b/.gitignore index c43deab..badf00b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,11 @@ local.properties .DS_Store captures/ .kotlin/ +CLAUDE.md + +# Release signing — never commit keystores or their credentials. +store-assets/ +app/keystore.properties +*.jks +*.keystore +*.pepk diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 504b655..0b7b24c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,5 @@ +import java.util.Properties + plugins { // AGP 9 built-in Kotlin (see :metawear-core) — org.jetbrains.kotlin.android // must NOT be applied. The Compose compiler Gradle plugin (2.2.10, matching @@ -6,6 +8,21 @@ plugins { alias(libs.plugins.kotlin.compose) } +// Release signing reads from an UNTRACKED file so no secret ever enters git. +// Create app/keystore.properties (gitignored) with: +// storeFile=/absolute/path/to/mbientlab_android_apps.jks +// storePassword=… +// keyAlias=… +// keyPassword=… +// When the file is absent (CI, fresh clones) the release build type still +// assembles — unsigned — so the AAB task exists everywhere and only a machine +// holding the upload key can produce a signed artifact. +val keystoreProperties = Properties().apply { + val f = rootProject.file("app/keystore.properties") + if (f.exists()) f.inputStream().use { load(it) } +} +val hasReleaseKeystore = keystoreProperties.getProperty("storeFile") != null + // Jetpack Compose MetaWear app: scanning, live streaming with ring-buffer // decimation, on-device logging + download, session history with CSV export, // LED/haptic controls, device settings, and firmware updates. @@ -17,8 +34,53 @@ android { applicationId = "com.mbientlab.metawear.app" minSdk = 26 targetSdk = 36 - versionCode = 1 - versionName = "1.0" + // This package already ships on Google Play (the previous-generation + // app reached versionCode 25 / 4.0.0). Play rejects any upload whose + // versionCode is not higher than the live one, so the new app line + // starts at 100 / 5.0.0 — clear headroom above the old series and an + // unmistakable major bump for existing users. + versionCode = 100 + versionName = "5.0.0" + } + + signingConfigs { + if (hasReleaseKeystore) { + create("release") { + storeFile = file(keystoreProperties.getProperty("storeFile")) + storePassword = keystoreProperties.getProperty("storePassword") + keyAlias = keystoreProperties.getProperty("keyAlias") + keyPassword = keystoreProperties.getProperty("keyPassword") + } + } + } + + buildTypes { + release { + // R8: shrink + obfuscate. Keep rules for Room, the Nordic BLE/DFU + // libraries, and Compose live in proguard-rules.pro. + isMinifyEnabled = true + isShrinkResources = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro", + ) + // Without an upload keystore on this machine, sign with the debug + // key so the minified build can still be installed and smoke-tested + // on a device. Play never sees this artifact — the store AAB is + // produced only where keystore.properties exists. + signingConfig = if (hasReleaseKeystore) signingConfigs.getByName("release") else signingConfigs.getByName("debug") + } + debug { + // Distinct id so a debug build can sit beside the store app. + applicationIdSuffix = ".debug" + versionNameSuffix = "-debug" + } + } + + bundle { + // Let Play deliver per-device splits; language splits stay off so + // the app is fully usable offline in any locale (single-language app). + language.enableSplit = false } buildFeatures { diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..be17183 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,32 @@ +# R8 keep rules for the MetaWear app release build. + +# --- Room --------------------------------------------------------------- +# Entities / DAOs are reached via generated code and reflection-free, but the +# @Database subclass and its generated _Impl are located by name. +-keep class * extends androidx.room.RoomDatabase { (); } +-keep @androidx.room.Entity class * { *; } +-dontwarn androidx.room.paging.** + +# --- Nordic Kotlin BLE library + Android DFU library -------------------- +# The DFU library instantiates the app's DfuBaseService subclass by name and +# talks to it over broadcasts; the BLE library uses reflection-free coroutines +# but ships some optional pieces R8 warns about. +-keep class com.mbientlab.metawear.firmware.MetaWearDfuService { *; } +-keep class no.nordicsemi.android.dfu.** { *; } +-dontwarn no.nordicsemi.android.** + +# --- kotlinx.coroutines / datetime --------------------------------------- +-dontwarn kotlinx.coroutines.** +-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory +-keepnames class kotlinx.coroutines.CoroutineExceptionHandler +-keepclassmembers class kotlinx.coroutines.** { volatile ; } +-dontwarn kotlinx.datetime.** + +# --- App: keep the persisted-record codec's data classes stable ------------ +# LogSessionRecord / RememberedDevice round-trip through hand-rolled string +# codecs (no reflection), so nothing here needs keeping — listed for clarity. + +# --- General --------------------------------------------------------------- +-keepattributes *Annotation*, Signature, InnerClasses, EnclosingMethod +-keepattributes SourceFile, LineNumberTable +-renamesourcefileattribute SourceFile