diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f02ee27421..05feff61cd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -142,8 +142,12 @@ android { buildTypes { getByName("release") { - isMinifyEnabled = false - + isMinifyEnabled = true + isShrinkResources = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + file("proguard-rules.pro") + ) devNetDefaultOn(false) enablePermissiveNetworkSecurityConfig(false) setAlternativeAppName(null) @@ -179,7 +183,6 @@ android { getByName("debug") { isDefault = true - isMinifyEnabled = false enableUnitTestCoverage = false signingConfig = signingConfigs.getByName("debug") @@ -357,6 +360,11 @@ dependencies { if (huaweiEnabled) { val huaweiImplementation = configurations.maybeCreate("huaweiImplementation") huaweiImplementation(libs.huawei.push) + + // These are compileOnly on the Huawei flavor so R8 can resolve optional HMS classes + // referenced by HMS Push during minification. + compileOnly(libs.huawei.hianalytics) + compileOnly(libs.huawei.availableupdate) } implementation(libs.androidx.media3.exoplayer) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000000..1db2e7d18e --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,213 @@ +########## BASELINE / ATTRIBUTES ########## +# Core attrs (serialization/DI/reflective access often rely on these) +-keepattributes *Annotation*,Signature,InnerClasses,EnclosingMethod,MethodParameters,Record + +# Honor @Keep if present +-keep @androidx.annotation.Keep class * { *; } +-keepclasseswithmembers class * { @androidx.annotation.Keep *; } + +########## OPTIONAL GOOGLE BITS (SUPPRESSED WARNINGS) ########## +-dontwarn com.google.android.gms.common.annotation.** +-dontwarn com.google.firebase.analytics.connector.** + +########## ANDROID / DI ########## +# Workers constructed by class name +-keep class ** extends androidx.work.ListenableWorker + +########## KOTLINX SERIALIZATION ########## +-keepclassmembers class ** { + @kotlinx.serialization.Serializable *; + *** Companion; + kotlinx.serialization.KSerializer serializer(...); +} + +########## JACKSON (CORE + ANNOTATIONS + DTOs) ########## +# Keep Jackson packages and common annotated members +-keep class com.fasterxml.jackson.** { *; } +-keepclassmembers class ** { + @com.fasterxml.jackson.annotation.JsonCreator (...); + @com.fasterxml.jackson.annotation.JsonProperty *; +} + +-keep class ** extends com.fasterxml.jackson.core.type.TypeReference { *; } +-keep class * implements com.fasterxml.jackson.databind.util.Converter { public (); public *; } +-keep class * extends com.fasterxml.jackson.databind.JsonDeserializer { public (); public *; } + +-dontwarn com.fasterxml.jackson.databind.** + +# Jackson DTO used by OpenGroupApi (reactions map values) +-keep class org.session.libsession.messaging.open_groups.OpenGroupApi$Reaction { *; } +-keepnames class org.session.libsession.messaging.open_groups.OpenGroupApi$Reaction +-keepclassmembers class org.session.libsession.messaging.open_groups.OpenGroupApi$Reaction { + ; + *** get*(); + void set*(***); + + # keep the default constructor too: + public (***, int, kotlin.jvm.internal.DefaultConstructorMarker); + # and a bare no-arg constructor if it exists + public (); +} + +# DTO used by OpenGroupApi +-keep class org.session.libsession.messaging.open_groups.OpenGroupApi$Capabilities { *; } +-keepclassmembers class org.session.libsession.messaging.open_groups.OpenGroupApi$Capabilities { (); } +-keepnames class org.session.libsession.messaging.open_groups.OpenGroupApi$Capabilities + +# Project models referenced via Jackson (from crashes) +-keep class org.thoughtcrime.securesms.crypto.KeyStoreHelper$SealedData { *; } +-keep class org.thoughtcrime.securesms.crypto.KeyStoreHelper$SealedData$* { *; } +-keep class org.thoughtcrime.securesms.crypto.AttachmentSecret { *; } +-keep class org.thoughtcrime.securesms.crypto.AttachmentSecret$* { *; } + +# Keep names + bean-style accessors for OpenGroupApi models +-keepnames class org.session.libsession.messaging.open_groups.** +-keepclassmembers class org.session.libsession.messaging.open_groups.** { + ; + *** get*(); + void set*(***); +} + +# Keep names + bean-style accessors for snode models +-keepnames class org.session.libsession.snode.** +-keepclassmembers class org.session.libsession.snode.** { + ; + *** get*(); + void set*(***); +} + +# Converters / Deserializers +-keep class org.session.libsession.snode.model.RetrieveMessageConverter { public (); public *; } + +########## JNI LOGGER / NATIVE ENTRYPOINTS ########## +# Logging interface & implementations (JNI looks up log(String,String,int)) +-keep interface network.loki.messenger.libsession_util.util.Logger { *; } +-keepnames class * implements network.loki.messenger.libsession_util.util.Logger +-keepclassmembers class * implements network.loki.messenger.libsession_util.util.Logger { + public void log(java.lang.String, java.lang.String, int); +} + +# JNI: ConfigPush constructors (exact signatures preserved) +-keepnames class network.loki.messenger.libsession_util.util.ConfigPush +-keepclassmembers class network.loki.messenger.libsession_util.util.ConfigPush { + public (java.util.List, long, java.util.List); + public (java.util.List, long, java.util.List, int, kotlin.jvm.internal.DefaultConstructorMarker); +} + +# JNI: specific getter used from native +-keepnames class network.loki.messenger.libsession_util.util.UserPic +-keepclassmembers class network.loki.messenger.libsession_util.util.UserPic { + public byte[] getKeyAsByteArray(); +} + +-keep class network.loki.messenger.libsession_util.util.GroupInfo$ClosedGroupInfo { *; } +-keepnames class network.loki.messenger.libsession_util.util.GroupInfo$ClosedGroupInfo +-keepclassmembers class network.loki.messenger.libsession_util.util.GroupInfo$ClosedGroupInfo { + public byte[] getAdminKeyAsByteArray(); + public byte[] getAuthDataAsByteArray(); +} + +########## WEBRTC / CHROMIUM JNI ########## +# WebRTC public Java APIs (kept for JNI_OnLoad registration) +-keep class org.webrtc.** { *; } + +# Chromium-based bits +-keep class org.chromium.base.** { *; } +-keep class org.chromium.net.** { *; } + +# Keep all native bridges everywhere +-keepclasseswithmembers,includedescriptorclasses class * { + native ; +} + +########## WEBRTC / CHROMIUM jni_zero ########## +# Ensure jni_zero Java side is discoverable by native +-keep class org.jni_zero.** { *; } +-keepnames class org.jni_zero.** + +########## CONVERSATION / MODELS (JNI + REFLECTION) ########## +# Conversation.* types constructed via JNI with (String,long,boolean) +-keepclassmembers class network.loki.messenger.libsession_util.util.Conversation$* { + public (java.lang.String, long, boolean); +} + +# Keep names and members of Conversation/Community models (JNI searches by name) +-keep class network.loki.messenger.libsession_util.util.Conversation$Community { *; } +-keep class network.loki.messenger.libsession_util.util.Conversation$OneToOne { *; } +-keep class network.loki.messenger.libsession_util.util.Conversation$ClosedGroup { *; } +-keep class network.loki.messenger.libsession_util.util.BaseCommunityInfo { *; } + +-keepclassmembers class network.loki.messenger.libsession_util.util.Conversation$Community { public (...); } +-keepclassmembers class network.loki.messenger.libsession_util.util.Conversation$OneToOne { public (...); } +-keepclassmembers class network.loki.messenger.libsession_util.util.Conversation$ClosedGroup { public (...); } + +-keepnames class network.loki.messenger.libsession_util.util.Conversation$Community +-keepnames class network.loki.messenger.libsession_util.util.Conversation$OneToOne +-keepnames class network.loki.messenger.libsession_util.util.Conversation$ClosedGroup +-keepnames class network.loki.messenger.libsession_util.util.BaseCommunityInfo + +# Group members (JNI constructor with long) +-keep class network.loki.messenger.libsession_util.GroupMembersConfig { *; } +-keep class network.loki.messenger.libsession_util.util.GroupMember { *; } +-keepclassmembers class network.loki.messenger.libsession_util.util.GroupMember { public (long); } +-keepnames class network.loki.messenger.libsession_util.util.GroupMember + +# Broad safety net for long-arg ctors in util package +-keepclassmembers class network.loki.messenger.libsession_util.util.** { public (long); } + +########## EMOJI SEARCH (JACKSON / POLYMORPHIC) ########## +# Keep names if @JsonTypeInfo uses CLASS/MINIMAL_CLASS +-keepnames class org.thoughtcrime.securesms.database.model.** +# Preserve abstract base + nested types for property/creator names +-keep class org.thoughtcrime.securesms.database.model.EmojiSearchData { *; } +-keep class org.thoughtcrime.securesms.database.model.EmojiSearchData$* { *; } + +########## KRYO (SERIALIZATION OF DESTINATIONS) ########## +# No-arg contructors required at runtime for these sealed subclasses +-keepclassmembers class org.session.libsession.messaging.messages.Destination$ClosedGroup { (); } +-keepclassmembers class org.session.libsession.messaging.messages.Destination$Contact { (); } +-keepclassmembers class org.session.libsession.messaging.messages.Destination$LegacyClosedGroup { (); } +-keepclassmembers class org.session.libsession.messaging.messages.Destination$LegacyOpenGroup { (); } +-keepclassmembers class org.session.libsession.messaging.messages.Destination$OpenGroup { (); } +-keepclassmembers class org.session.libsession.messaging.messages.Destination$OpenGroupInbox { (); } + +# Keep the Enum serializer contructor Kryo reflects on +-keepclassmembers class com.esotericsoftware.kryo.serializers.** { + public (...); +} + +# Prevent enum unboxing/renaming for the enum field being serialized +-keep class org.session.libsession.messaging.messages.control.TypingIndicator$Kind { *; } + +# Preserve class names for Kryo +-keepnames class org.session.libsession.messaging.messages.Destination$** + +########## OPEN GROUP API (MESSAGES) ########## +-keep class org.session.libsession.messaging.open_groups.OpenGroupApi$Message { *; } +-keepclassmembers class org.session.libsession.messaging.open_groups.OpenGroupApi$Message { (); } +-keepnames class org.session.libsession.messaging.open_groups.OpenGroupApi$Message +-keepclassmembers class org.session.libsession.messaging.open_groups.OpenGroupApi$Message { + *** get*(); + void set*(***); +} + +-keep class org.session.libsession.messaging.utilities.UpdateMessageData { *; } +-keep class org.session.libsession.messaging.utilities.UpdateMessageData$* { *; } +-keepnames class org.session.libsession.messaging.utilities.UpdateMessageData$* + +########## HUAWEI / HMS (minified builds) ########## +# Device-only classes referenced by HMS internals — not present on Maven. +-dontwarn android.telephony.HwTelephonyManager +-dontwarn com.huawei.android.os.BuildEx$VERSION +-dontwarn com.huawei.libcore.io.** +-dontwarn com.huawei.hianalytics.** +-dontwarn com.huawei.hms.availableupdate.** + +# Misc suppressed warnings +-dontwarn java.beans.BeanInfo +-dontwarn java.beans.IntrospectionException +-dontwarn java.beans.Introspector +-dontwarn java.beans.PropertyDescriptor +-dontwarn java.lang.management.ManagementFactory +-dontwarn java.lang.management.RuntimeMXBean +-dontwarn sun.nio.ch.DirectBuffer \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 505404eaeb..545abeedb6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -68,6 +68,7 @@ uiTestJunit4Version = "1.9.4" workRuntimeKtxVersion = "2.11.0" zxingVersion = "3.5.4" huaweiPushVersion = "6.13.0.300" +huaweiAnalyticsVersion = "6.12.0.301" googlePlayReviewVersion = "2.0.2" coilVersion = "3.3.0" billingVersion = "8.0.0" @@ -165,6 +166,8 @@ subsampling-scale-image-view = { module = "com.davemorrissey.labs:subsampling-sc truth = { module = "com.google.truth:truth", version.ref = "truthVersion" } turbine = { module = "app.cash.turbine:turbine", version.ref = "turbineVersion" } huawei-push = { module = 'com.huawei.hms:push', version.ref = 'huaweiPushVersion' } +huawei-hianalytics = { module = "com.huawei.hms:hianalytics", version.ref = "huaweiAnalyticsVersion" } +huawei-availableupdate = { module = "com.huawei.hms:availableupdate", version.ref = "huaweiPushVersion" } google-play-review = { module = "com.google.android.play:review", version.ref = "googlePlayReviewVersion" } google-play-review-ktx = { module = "com.google.android.play:review-ktx", version.ref = "googlePlayReviewVersion" } sqlite-web-viewer = { module = "io.github.simophin:sqlite-web-viewer", version = "0.0.3" }