# Proguard Rules for Amethyst Proguard configuration for optimizing and obfuscating Android APK while preserving necessary code. ## What is Proguard/R8? **R8** is Android's default code shrinker and obfuscator (replaced Proguard in AGP 3.4.0+). It: - **Shrinks** code by removing unused classes/methods - **Obfuscates** code by renaming classes/methods to short names - **Optimizes** code by inlining methods and removing dead code ## Amethyst Proguard Configuration **File:** `amethyst/proguard-rules.pro` ### Keep Kotlin Metadata ```proguard # Kotlin metadata is required for reflection -keep class kotlin.Metadata { *; } -keep class kotlin.** { *; } -dontwarn kotlin.** # Kotlin serialization -keepattributes *Annotation*, InnerClasses -dontnote kotlinx.serialization.AnnotationsKt -dontnote kotlinx.serialization.SerializationKt -keep,includedescriptorclasses class com.vitorpamplona.**$$serializer { *; } -keepclassmembers class com.vitorpamplona.** { *** Companion; } -keepclasseswithmembers class com.vitorpamplona.** { kotlinx.serialization.KSerializer serializer(...); } ``` ### Keep Nostr Event Classes ```proguard # Nostr events are serialized/deserialized -keep class com.vitorpamplona.quartz.events.** { *; } -keep class com.vitorpamplona.quartz.encoders.** { *; } # Keep event builders -keep class com.vitorpamplona.quartz.builders.** { *; } # Keep tag classes -keep class com.vitorpamplona.quartz.nip01Core.tags.** { *; } ``` ### Keep Data Classes ```proguard # Data classes used in ViewModels and serialization -keep @kotlinx.serialization.Serializable class * { *; } # Keep all data classes -keep class com.vitorpamplona.amethyst.model.** { *; } -keep class com.vitorpamplona.amethyst.service.model.** { *; } ``` ### Keep Compose Classes ```proguard # Jetpack Compose -keep class androidx.compose.** { *; } -dontwarn androidx.compose.** # Compose runtime -keep class androidx.compose.runtime.** { *; } # Compose UI -keep class androidx.compose.ui.** { *; } # Material3 -keep class androidx.compose.material3.** { *; } # Navigation Compose - Keep serializable routes -keep class * implements java.io.Serializable { *; } -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; !static !transient ; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } ``` ### Keep OkHttp/Retrofit ```proguard # OkHttp -dontwarn okhttp3.** -dontwarn okio.** -keep class okhttp3.** { *; } -keep class okio.** { *; } # OkHttp WebSockets (for Nostr relays) -keep class okhttp3.internal.ws.** { *; } # Retrofit (if used) -keepattributes Signature -keepattributes Exceptions -keep class retrofit2.** { *; } ``` ### Keep Jackson (JSON) ```proguard # Jackson JSON library -keep class com.fasterxml.jackson.** { *; } -keep class org.codehaus.** { *; } -keepclassmembers class * { @com.fasterxml.jackson.annotation.* ; } # Jackson polymorphic types -keepattributes RuntimeVisibleAnnotations -keep @com.fasterxml.jackson.annotation.JsonTypeInfo class * ``` ### Keep Secp256k1 (Crypto) ```proguard # Secp256k1 native library -keep class fr.acinq.secp256k1.** { *; } # Keep native methods -keepclasseswithmembernames class * { native ; } ``` ### Keep Tor ```proguard # Tor library -keep class com.msopentech.thali.toronionproxy.** { *; } -dontwarn com.msopentech.thali.toronionproxy.** ``` ### Keep ExoPlayer (Media) ```proguard # ExoPlayer (Media3) -keep class androidx.media3.** { *; } -dontwarn androidx.media3.** -keep class com.google.android.exoplayer2.** { *; } -dontwarn com.google.android.exoplayer2.** ``` ### Keep Coil (Image Loading) ```proguard # Coil image loading -keep class coil.** { *; } -keep class coil3.** { *; } -dontwarn coil.** -dontwarn coil3.** ``` ### Keep ViewModels ```proguard # ViewModel classes -keep class * extends androidx.lifecycle.ViewModel { (); } # ViewModel factories -keep class * extends androidx.lifecycle.ViewModelProvider$Factory { (...); } # Keep ViewModel constructors for reflection -keepclassmembers class * extends androidx.lifecycle.ViewModel { (...); } ``` ### Keep Parcelable ```proguard # Parcelable -keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *; } -keepclassmembers class * implements android.os.Parcelable { public ; private ; } ``` ### Keep Enums ```proguard # Enums -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } ``` ### Remove Logging (Production) ```proguard # Remove debug logging in release builds -assumenosideeffects class android.util.Log { public static *** d(...); public static *** v(...); public static *** i(...); } # Keep error/warning logs -assumenosideeffects class android.util.Log { public static *** e(...) return false; public static *** w(...) return false; } ``` ### Keep Crashlytics/Firebase ```proguard # Firebase Crashlytics -keepattributes SourceFile,LineNumberTable -keep public class * extends java.lang.Exception # Firebase -keep class com.google.firebase.** { *; } -dontwarn com.google.firebase.** ``` ## Build Configuration ### Enable R8 in build.gradle ```gradle android { buildTypes { release { minifyEnabled = true shrinkResources = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } debug { minifyEnabled = false } } } ``` ### Multiple Proguard Files ```gradle android { buildTypes { release { proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro", "proguard-quartz.pro", // Library-specific rules "proguard-compose.pro" // Compose-specific rules ) } } } ``` ## Debugging Proguard Issues ### Generate Mapping File R8 generates `mapping.txt` in `app/build/outputs/mapping/release/`: ``` # Original class name -> Obfuscated name com.vitorpamplona.amethyst.ui.MainActivity -> a.b.c: void onCreate(Bundle) -> a ``` ### Deobfuscate Stack Traces ```bash # Using retrace (part of Android SDK) retrace.sh mapping.txt stacktrace.txt ``` ### Enable Proguard Output ```gradle android { buildTypes { release { proguardFiles(...) // Generate reports postprocessing { proguardFiles = [...] obfuscate = true optimizeCode = true removeUnusedCode = true } } } } ``` **Output files:** - `build/outputs/mapping/release/configuration.txt` - All Proguard rules applied - `build/outputs/mapping/release/mapping.txt` - Obfuscation mappings - `build/outputs/mapping/release/seeds.txt` - Classes kept by `-keep` rules - `build/outputs/mapping/release/usage.txt` - Code removed by R8 ### Test Release Build ```bash ./gradlew assembleRelease # Install and test adb install app/build/outputs/apk/release/app-release.apk ``` ## Common Issues ### Issue: NoSuchMethodException at Runtime **Cause:** Proguard removed or renamed a method used via reflection. **Solution:** ```proguard -keep class com.example.YourClass { public ; } ``` ### Issue: Serialization Fails **Cause:** Data class fields were renamed. **Solution:** ```proguard -keep @kotlinx.serialization.Serializable class * { *; } -keepclassmembers class * { @kotlinx.serialization.SerialName ; } ``` ### Issue: Compose Navigation Crashes **Cause:** @Serializable route classes were obfuscated. **Solution:** ```proguard # Keep all route classes -keep @kotlinx.serialization.Serializable class com.vitorpamplona.amethyst.ui.navigation.routes.** { *; } ``` ### Issue: Native Library Crashes **Cause:** Native method signatures were changed. **Solution:** ```proguard -keepclasseswithmembernames class * { native ; } ``` ## Optimization Tips ### 1. Keep Only What's Necessary Don't use broad wildcards: ```proguard # Bad - keeps everything -keep class com.vitorpamplona.** { *; } # Good - keeps only specific packages -keep class com.vitorpamplona.quartz.events.** { *; } ``` ### 2. Test Thoroughly - Test all app features after enabling Proguard - Test deep links and navigation - Test serialization/deserialization - Test external library integrations ### 3. Use AGP's Proguard Analysis ```gradle android { buildTypes { release { // Generate R8 configuration android.debug.obsoleteApi = true } } } ``` ### 4. Analyze APK Size ```bash # Build release APK ./gradlew assembleRelease # Analyze APK with Android Studio # Build > Analyze APK > Select app-release.apk ``` See `scripts/analyze-apk-size.sh` for automated analysis. ## Product Flavor Specific Rules ### Play Flavor (Firebase) ```proguard # proguard-play.pro -keep class com.google.firebase.** { *; } -keep class com.google.android.gms.** { *; } ``` ### F-Droid Flavor (No Google Services) ```proguard # proguard-fdroid.pro # UnifiedPush -keep class org.unifiedpush.** { *; } ``` **Configure in build.gradle:** ```gradle android { flavorDimensions = ["channel"] productFlavors { create("play") { dimension = "channel" proguardFiles("proguard-play.pro") } create("fdroid") { dimension = "channel" proguardFiles("proguard-fdroid.pro") } } } ``` ## File Locations - `amethyst/proguard-rules.pro` - Main Proguard rules - `amethyst/build/outputs/mapping/release/` - Proguard output files - `amethyst/build.gradle` - Proguard configuration ## Resources - [Android R8 Documentation](https://developer.android.com/build/shrink-code) - [Proguard Manual](https://www.guardsquare.com/manual/configuration) - [Kotlinx Serialization Proguard](https://github.com/Kotlin/kotlinx.serialization#android)