Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,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)
Expand Down Expand Up @@ -188,7 +192,6 @@ android {

getByName("debug") {
isDefault = true
isMinifyEnabled = false
enableUnitTestCoverage = false
signingConfig = signingConfigs.getByName("debug")

Expand Down Expand Up @@ -373,6 +376,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)
Expand Down
213 changes: 213 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -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 <init>(...);
@com.fasterxml.jackson.annotation.JsonProperty *;
}

-keep class ** extends com.fasterxml.jackson.core.type.TypeReference { *; }
-keep class * implements com.fasterxml.jackson.databind.util.Converter { public <init>(); public *; }
-keep class * extends com.fasterxml.jackson.databind.JsonDeserializer { public <init>(); 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 {
<fields>;
*** get*();
void set*(***);

# keep the default constructor too:
public <init>(***, int, kotlin.jvm.internal.DefaultConstructorMarker);
# and a bare no-arg constructor if it exists
public <init>();
}

# DTO used by OpenGroupApi
-keep class org.session.libsession.messaging.open_groups.OpenGroupApi$Capabilities { *; }
-keepclassmembers class org.session.libsession.messaging.open_groups.OpenGroupApi$Capabilities { <init>(); }
-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.** {
<fields>;
*** get*();
void set*(***);
}

# Keep names + bean-style accessors for snode models
-keepnames class org.session.libsession.snode.**
-keepclassmembers class org.session.libsession.snode.** {
<fields>;
*** get*();
void set*(***);
}

# Converters / Deserializers
-keep class org.session.libsession.snode.model.RetrieveMessageConverter { public <init>(); 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 <init>(java.util.List, long, java.util.List);
public <init>(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 <methods>;
}

########## 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 <init>(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 <init>(...); }
-keepclassmembers class network.loki.messenger.libsession_util.util.Conversation$OneToOne { public <init>(...); }
-keepclassmembers class network.loki.messenger.libsession_util.util.Conversation$ClosedGroup { public <init>(...); }

-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 <init>(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 <init>(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 { <init>(); }
-keepclassmembers class org.session.libsession.messaging.messages.Destination$Contact { <init>(); }
-keepclassmembers class org.session.libsession.messaging.messages.Destination$LegacyClosedGroup { <init>(); }
-keepclassmembers class org.session.libsession.messaging.messages.Destination$LegacyOpenGroup { <init>(); }
-keepclassmembers class org.session.libsession.messaging.messages.Destination$OpenGroup { <init>(); }
-keepclassmembers class org.session.libsession.messaging.messages.Destination$OpenGroupInbox { <init>(); }

# Keep the Enum serializer contructor Kryo reflects on
-keepclassmembers class com.esotericsoftware.kryo.serializers.** {
public <init>(...);
}

# 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 { <init>(); }
-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
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
Expand All @@ -35,8 +34,6 @@
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.bumptech.glide.RequestManager;
import com.squareup.phrase.Phrase;
Expand Down Expand Up @@ -349,13 +346,13 @@ public static void selectGallery(Activity activity, int requestCode, @NonNull Ad
.execute();
}

public static boolean hasFullAccess(Activity activity) {
public static boolean hasFullAccess(Context c) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return Permissions.hasAll(activity,
return Permissions.hasAll(c,
Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO);
} else {
return Permissions.hasAll(activity, android.Manifest.permission.READ_EXTERNAL_STORAGE);
return Permissions.hasAll(c, android.Manifest.permission.READ_EXTERNAL_STORAGE);
}
}

Expand Down Expand Up @@ -386,9 +383,9 @@ public static void managePhotoAccess(@NonNull Activity activity, @Nullable Runna
}
}

public static boolean shouldShowManagePhoto(@NonNull Activity activity){
public static boolean shouldShowManagePhoto(@NonNull Context c){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE){
return !hasFullAccess(activity) && hasPartialAccess(activity);
return !hasFullAccess(c) && hasPartialAccess(c);
}else{
// No partial access for <= API 33
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -622,39 +622,35 @@ class GroupManagerV2Impl @Inject constructor(
.mapNotNull { (member, result) ->
configs.groupMembers.get(member.hexString)?.apply {
if (result.isFailure) {
configs.groupMembers.get(member.hexString)?.let { member ->
member.setPromotionFailed()
configs.groupMembers.set(member)
}
setPromotionFailed()
}
}
}
.forEach(configs.groupMembers::set)
}

if (!isRepromote) {
messageSender.sendAndAwait(message, Address.fromSerialized(group.hexString))
}

val failedMembers = promotedByMemberIDs
.filterValues { it.isFailure }
.keys
.toList()
.map { it.hexString }

if (failedMembers.isNotEmpty()) {
val cause = promotedByMemberIDs.values
.firstOrNull { it.isFailure }
?.exceptionOrNull()
.firstOrNull { it.isFailure }?.exceptionOrNull()
?: RuntimeException("Failed to promote ${failedMembers.size} member(s)")

throw GroupInviteException(
isPromotion = true,
inviteeAccountIds = failedMembers.map { it.hexString },
inviteeAccountIds = failedMembers,
groupName = groupName ?: "",
isReinvite = isRepromote,
underlying = cause
)
}

if (!isRepromote) {
messageSender.sendAndAwait(message, Address.fromSerialized(group.hexString))
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ class InviteMembersViewModel @AssistedInject constructor(
selected: Set<SelectedContact>,
): InviteContactsDialogState {
val count = selected.size
val firstMember = selected.firstOrNull()
val sortedMembers = selected.sortedBy { it.address }
val firstMember = sortedMembers.firstOrNull()

val body: CharSequence = when (count) {
1 -> {
Expand All @@ -103,7 +104,7 @@ class InviteMembersViewModel @AssistedInject constructor(
}
}
2 -> {
val secondMember = selected.elementAtOrNull(1)?.name
val secondMember = sortedMembers.elementAtOrNull(1)?.name
Phrase.from(context, R.string.membersInviteShareDescriptionTwo)
.put(NAME_KEY, firstMember?.name)
.put(OTHER_NAME_KEY, secondMember)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,8 @@ class ManageGroupMembersViewModel @AssistedInject constructor(
group: String
): RemoveMembersDialogState {
val count = selected.size
val firstMember = selected.firstOrNull()
val sortedMembers = selected.sortedBy { it.accountId }
val firstMember = sortedMembers.firstOrNull()

val body: CharSequence = when (count) {
1 -> Phrase.from(context, R.string.groupRemoveDescription)
Expand All @@ -312,7 +313,7 @@ class ManageGroupMembersViewModel @AssistedInject constructor(
.format()

2 -> {
val secondMember = selected.elementAtOrNull(1)?.name
val secondMember = sortedMembers.elementAtOrNull(1)?.name
Phrase.from(context, R.string.groupRemoveDescriptionTwo)
.put(NAME_KEY, firstMember?.name)
.put(OTHER_NAME_KEY, secondMember)
Expand Down
Loading