From b05c6b99bb7d6fd560689ed74a6dc9d34d6869f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Xu=C3=A2n=20D=C5=A9ng?= Date: Mon, 12 Oct 2026 23:06:31 +0700 Subject: [PATCH 1/2] build(android): enable R8 on release builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Play reports almost no DEX obfuscation while minify is off. Release now shrinks and obfuscates (versionCode 5), with keep rules for the Netty and serialization paths that otherwise fail at startup. Signed-off-by: Hồ Xuân Dũng --- app/build.gradle.kts | 10 +++- app/proguard-rules.pro | 119 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 app/proguard-rules.pro diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4ffa2d5..40ed024 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -28,7 +28,7 @@ android { applicationId = "com.handlive.android" minSdk = 29 targetSdk = 36 - versionCode = 4 + versionCode = 5 versionName = "0.1.0-beta.3" // CONN-03: `{RELAY_HOST}` is configured at build time (0.4.3); without it the build has no relay. The pins are @@ -81,7 +81,13 @@ android { isPseudoLocalesEnabled = true } release { - isMinifyEnabled = false + // Play Vitals: R8 obfuscates and shrinks the release DEX. Ship mapping.txt with the Play upload. + isMinifyEnabled = true + isShrinkResources = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro", + ) } } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..e34aa92 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,119 @@ +# kotlinx-serialization looks up Companion.serializer() by name on @Serializable types. +# Scope is our code: core.protocol plus the SMS and call cursors, which are @Serializable and encoded at runtime. +-keepattributes RuntimeVisibleAnnotations,AnnotationDefault,InnerClasses,Signature,EnclosingMethod + +-if @kotlinx.serialization.Serializable class app.handlive.android.** +-keepclassmembers class <1> { + static <1>$Companion Companion; +} + +-if @kotlinx.serialization.Serializable class app.handlive.android.** { + static **$* *; +} +-keepclassmembers class <2>$<3> { + kotlinx.serialization.KSerializer serializer(...); +} + +-if @kotlinx.serialization.Serializable class app.handlive.android.** { + public static ** INSTANCE; +} +-keepclassmembers class <1> { + public static <1> INSTANCE; + kotlinx.serialization.KSerializer serializer(...); +} + +# Tink Android 1.23.0 ships only META-INF/proguard/protobuf.pro. AeadConfig.register, AndroidKeysetManager +# and the subtle primitives (X25519, Ed25519, XChaCha20-Poly1305) are referenced directly, so no extra keep. + +# firebase-messaging 25.1.3 has no consumer rules. gms component discovery instantiates registrars named in manifest meta-data. +-keep class * extends com.google.firebase.components.ComponentRegistrar { + public (); +} + +# Ktor still names Netty HTTP/3 and QUIC types; those modules are excluded and the server leaves HTTP/3 off. +-dontwarn io.netty.handler.codec.http3.Http3 +-dontwarn io.netty.handler.codec.http3.Http3Headers +-dontwarn io.netty.handler.codec.http3.Http3HeadersFrame +-dontwarn io.netty.handler.codec.http3.Http3RequestStreamInboundHandler +-dontwarn io.netty.handler.codec.http3.Http3ServerConnectionHandler +-dontwarn io.netty.handler.codec.quic.EpollQuicUtils +-dontwarn io.netty.handler.codec.quic.QuicChannel +-dontwarn io.netty.handler.codec.quic.QuicChannelOption +-dontwarn io.netty.handler.codec.quic.QuicCodecBuilder +-dontwarn io.netty.handler.codec.quic.QuicCodecDispatcher +-dontwarn io.netty.handler.codec.quic.QuicConnectionIdGenerator +-dontwarn io.netty.handler.codec.quic.QuicServerCodecBuilder +-dontwarn io.netty.handler.codec.quic.QuicSslContext +-dontwarn io.netty.handler.codec.quic.QuicSslContextBuilder +-dontwarn io.netty.handler.codec.quic.QuicStreamChannel +-dontwarn io.netty.handler.codec.quic.QuicTokenHandler +-dontwarn io.netty.handler.codec.quic.SegmentedDatagramPacketAllocator + +# Netty's OpenSSL path (tcnative) is not on Android; the server uses the JDK TLS provider. +-dontwarn io.netty.internal.tcnative.AsyncSSLPrivateKeyMethod +-dontwarn io.netty.internal.tcnative.AsyncTask +-dontwarn io.netty.internal.tcnative.Buffer +-dontwarn io.netty.internal.tcnative.CertificateCallback +-dontwarn io.netty.internal.tcnative.CertificateCompressionAlgo +-dontwarn io.netty.internal.tcnative.CertificateVerifier +-dontwarn io.netty.internal.tcnative.Library +-dontwarn io.netty.internal.tcnative.SSL +-dontwarn io.netty.internal.tcnative.SSLContext +-dontwarn io.netty.internal.tcnative.SSLPrivateKeyMethod +-dontwarn io.netty.internal.tcnative.SSLSessionCache +-dontwarn io.netty.internal.tcnative.SessionTicketKey +-dontwarn io.netty.internal.tcnative.SniHostNameMatcher + +# JDK management and JFR APIs that Netty and Ktor probe, absent from the Android runtime. +-dontwarn java.lang.management.ManagementFactory +-dontwarn java.lang.management.RuntimeMXBean +-dontwarn javax.naming.ldap.LdapName +-dontwarn javax.naming.ldap.Rdn +-dontwarn jdk.jfr.Category +-dontwarn jdk.jfr.DataAmount +-dontwarn jdk.jfr.Description +-dontwarn jdk.jfr.Enabled +-dontwarn jdk.jfr.Event +-dontwarn jdk.jfr.FlightRecorder +-dontwarn jdk.jfr.Label +-dontwarn jdk.jfr.MemoryAddress +-dontwarn jdk.jfr.Name + +# kotlin-reflect is excluded from the server classpath; Ktor only uses it for dev auto-reload. +-dontwarn kotlin.reflect.full.KCallables +-dontwarn kotlin.reflect.full.KClasses +-dontwarn kotlin.reflect.jvm.ReflectJvmMapping + +# Netty's ReflectiveChannelFactory calls Class.getConstructor() on these channels. +# -keep, not -keepclassmembers: R8 otherwise inlines the no-arg constructor away. +-keep class io.netty.channel.socket.nio.NioServerSocketChannel { + public (); +} +-keep class io.netty.channel.socket.nio.NioSocketChannel { + public (); +} +-keep class io.netty.channel.socket.nio.NioDatagramChannel { + public (); +} + +# Netty calls Class.getDeclaredMethod by name (ConcurrentSkipListIntObjMultimap.acquireFenceFallback) +# and R8 also drops abstract methods that only satisfy a buffer interface. ART then rejects the class. +# Member names stay. Class names can still be obfuscated, and unused classes can still be removed. +# SSL, tcnative, HTTP/3 and QUIC stay out: those members reference classes that are not on Android. +-keepclassmembers class !io.netty.handler.ssl.**, !io.netty.internal.tcnative.**, !io.netty.handler.codec.http3.**, !io.netty.handler.codec.quic.**, io.netty.** { + *; +} + +# Reactor BlockHound is an optional service file Netty references; the class is not on Android. +-dontwarn reactor.blockhound.integration.BlockHoundIntegration + +# Optional Netty log backends that are not dependencies. +-dontwarn org.apache.log4j.Level +-dontwarn org.apache.log4j.Logger +-dontwarn org.apache.log4j.Priority +-dontwarn org.apache.logging.log4j.Level +-dontwarn org.apache.logging.log4j.LogManager +-dontwarn org.apache.logging.log4j.Logger +-dontwarn org.apache.logging.log4j.message.MessageFactory +-dontwarn org.apache.logging.log4j.spi.ExtendedLogger +-dontwarn org.apache.logging.log4j.spi.ExtendedLoggerWrapper From 67dcbdfc66d2e6116fc5e82af2a7304cc55dd209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Xu=C3=A2n=20D=C5=A9ng?= Date: Mon, 12 Oct 2026 23:34:54 +0700 Subject: [PATCH 2/2] build(android): keep Netty loggers from R8 class merging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit keepclassmembers still let R8 merge Log4J2Logger into a concrete class whose log methods stayed abstract. Keeping the logging package stops that. Signed-off-by: Hồ Xuân Dũng --- app/proguard-rules.pro | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index e34aa92..6bb3639 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -104,6 +104,12 @@ *; } +# -keep, not -keepclassmembers: R8 otherwise merges Log4J2Logger into a concrete class +# whose InternalLogger methods (debug, info, warn, error, trace) stay abstract. +-keep class io.netty.util.internal.logging.** { + *; +} + # Reactor BlockHound is an optional service file Netty references; the class is not on Android. -dontwarn reactor.blockhound.integration.BlockHoundIntegration