diff --git a/app/src/benchmarkShared/java/org/signal/benchmark/BenchmarkCommandReceiver.kt b/app/src/benchmarkShared/java/org/signal/benchmark/BenchmarkCommandReceiver.kt index 5e27109074..90037c3d37 100644 --- a/app/src/benchmarkShared/java/org/signal/benchmark/BenchmarkCommandReceiver.kt +++ b/app/src/benchmarkShared/java/org/signal/benchmark/BenchmarkCommandReceiver.kt @@ -15,10 +15,10 @@ import org.signal.benchmark.setup.Generator import org.signal.benchmark.setup.Harness import org.signal.benchmark.setup.OtherClient import org.signal.core.util.ThreadUtil -import org.thoughtcrime.securesms.dependencies.AppDependencies import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.TestDbUtils +import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.recipients.Recipient import org.whispersystems.signalservice.internal.push.Envelope diff --git a/app/src/benchmarkShared/java/org/signal/benchmark/BenchmarkSetupActivity.kt b/app/src/benchmarkShared/java/org/signal/benchmark/BenchmarkSetupActivity.kt index 73afa423eb..88f401e946 100644 --- a/app/src/benchmarkShared/java/org/signal/benchmark/BenchmarkSetupActivity.kt +++ b/app/src/benchmarkShared/java/org/signal/benchmark/BenchmarkSetupActivity.kt @@ -13,7 +13,6 @@ import kotlinx.coroutines.launch import org.signal.benchmark.setup.Harness import org.signal.benchmark.setup.TestMessages import org.signal.benchmark.setup.TestUsers -import org.signal.core.models.ServiceId.PNI import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.BaseActivity import org.thoughtcrime.securesms.backup.v2.BackupRepository diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt index d232937d0a..717027bc9d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt @@ -43,7 +43,6 @@ import org.signal.core.util.delete import org.signal.core.util.deleteAll import org.signal.core.util.exists import org.signal.core.util.forEach -import org.signal.core.util.forceForeignKeyConstraintsEnabled import org.signal.core.util.insertInto import org.signal.core.util.logging.Log import org.signal.core.util.readToList diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageConverter.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageConverter.kt index e2e0ef81ce..9167535e74 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageConverter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageConverter.kt @@ -40,6 +40,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.GroupJoinRequestUpdate import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberAddedUpdate import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberJoinedByLinkUpdate import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberJoinedUpdate +import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberLabelAccessLevelChangeUpdate import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberLeftUpdate import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberRemovedUpdate import org.thoughtcrime.securesms.backup.v2.proto.GroupMembershipAccessLevelChangeUpdate @@ -146,6 +147,7 @@ object GroupsV2UpdateMessageConverter { translateNewTimer(change, editorUnknown, updates) translateNewAttributeAccess(change, editorUnknown, updates) translateNewMembershipAccess(change, editorUnknown, updates) + translateNewMemberLabelAccess(change, editorUnknown, updates) translateNewGroupInviteLinkAccess(previousGroupState, change, editorUnknown, updates) translateRequestingMembers(selfIds, change, editorUnknown, updates) translateRequestingMemberApprovals(selfIds, change, editorUnknown, updates) @@ -437,6 +439,21 @@ object GroupsV2UpdateMessageConverter { } } + @JvmStatic + fun translateNewMemberLabelAccess(change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList) { + if (change.newMemberLabelAccess !== AccessRequired.UNKNOWN) { + val editorAci = if (editorUnknown) null else change.editorServiceIdBytes + updates.add( + GroupChangeChatUpdate.Update( + groupMemberLabelAccessLevelChangeUpdate = GroupMemberLabelAccessLevelChangeUpdate( + updaterAci = editorAci, + accessLevel = translateGv2AccessLevel(change.newMemberLabelAccess) + ) + ) + ) + } + } + @JvmStatic fun translateNewGroupInviteLinkAccess(previousGroupState: DecryptedGroup?, change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList) { var previousAccessControl: AccessRequired? = null diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java index d82e75ba1b..aa85732769 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java @@ -11,7 +11,9 @@ import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; import androidx.core.content.ContextCompat; +import org.signal.core.models.ServiceId; import org.signal.core.util.BidiUtil; +import org.signal.core.util.UuidUtil; import org.signal.storageservice.storage.protos.groups.AccessControl; import org.signal.storageservice.storage.protos.groups.Member; import org.signal.storageservice.storage.protos.groups.local.DecryptedApproveMember; @@ -46,6 +48,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.GroupJoinRequestUpdate; import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberAddedUpdate; import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberJoinedByLinkUpdate; import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberJoinedUpdate; +import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberLabelAccessLevelChangeUpdate; import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberLeftUpdate; import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberRemovedUpdate; import org.thoughtcrime.securesms.backup.v2.proto.GroupMembershipAccessLevelChangeUpdate; @@ -66,9 +69,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.ExpirationUtil; import org.thoughtcrime.securesms.util.SpanUtil; import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil; -import org.signal.core.models.ServiceId; import org.whispersystems.signalservice.api.push.ServiceIds; -import org.signal.core.util.UuidUtil; import java.util.Arrays; import java.util.Collections; @@ -165,6 +166,8 @@ final class GroupsV2UpdateMessageProducer { describeGroupMembershipAccessLevelChange(update.groupMembershipAccessLevelChangeUpdate, updates); } else if (update.groupAttributesAccessLevelChangeUpdate != null) { describeGroupAttributesAccessLevelChange(update.groupAttributesAccessLevelChangeUpdate, updates); + } else if (update.groupMemberLabelAccessLevelChangeUpdate != null) { + describeGroupMemberLabelAccessLevelChange(update.groupMemberLabelAccessLevelChangeUpdate, updates); } else if (update.groupAnnouncementOnlyChangeUpdate != null) { describeGroupAnnouncementOnlyUpdate(update.groupAnnouncementOnlyChangeUpdate, updates); } else if (update.groupAdminStatusUpdate != null) { @@ -592,6 +595,24 @@ final class GroupsV2UpdateMessageProducer { } } + private void describeGroupMemberLabelAccessLevelChange(@NonNull GroupMemberLabelAccessLevelChangeUpdate update, @NonNull List updates) { + if (update.accessLevel == GroupV2AccessLevel.UNKNOWN) { + return; + } + + String accessLevel = GV2AccessLevelUtil.toString(context, backupGv2AccessLevelToGroups(update.accessLevel)); + if (update.updaterAci == null) { + updates.add(updateDescription(context.getString(R.string.MessageRecord_unknown_admin_changed_who_can_add_member_labels_to_s, accessLevel), Glyph.MEGAPHONE)); + } else { + boolean editorIsYou = selfIds.matches(update.updaterAci); + if (editorIsYou) { + updates.add(updateDescription(context.getString(R.string.MessageRecord_you_changed_who_can_add_member_labels_to_s, accessLevel), Glyph.MEGAPHONE)); + } else { + updates.add(updateDescription(R.string.MessageRecord_s_changed_who_can_add_member_labels_to_s, update.updaterAci, accessLevel, Glyph.MEGAPHONE)); + } + } + } + private void describeGroupAnnouncementOnlyUpdate(@NonNull GroupAnnouncementOnlyChangeUpdate update, @NonNull List updates) { if (update.updaterAci == null) { if (update.isAnnouncementOnly) { @@ -707,6 +728,7 @@ final class GroupsV2UpdateMessageProducer { describeUnknownEditorNewTimer(change, updates); describeUnknownEditorNewAttributeAccess(change, updates); describeUnknownEditorNewMembershipAccess(change, updates); + describeUnknownEditorNewMemberLabelAccess(change, updates); describeUnknownEditorNewGroupInviteLinkAccess(previousGroupState, change, updates); describeRequestingMembers(change, updates); describeUnknownEditorRequestingMembersApprovals(change, updates); @@ -733,6 +755,7 @@ final class GroupsV2UpdateMessageProducer { describeNewTimer(change, updates); describeNewAttributeAccess(change, updates); describeNewMembershipAccess(change, updates); + describeNewMemberLabelAccess(change, updates); describeNewGroupInviteLinkAccess(previousGroupState, change, updates); describeRequestingMembers(change, updates); describeRequestingMembersApprovals(change, updates); @@ -1223,6 +1246,26 @@ final class GroupsV2UpdateMessageProducer { } } + private void describeNewMemberLabelAccess(@NonNull DecryptedGroupChange change, @NonNull List updates) { + boolean editorIsYou = selfIds.matches(change.editorServiceIdBytes); + + if (change.newMemberLabelAccess != AccessControl.AccessRequired.UNKNOWN) { + String accessLevel = GV2AccessLevelUtil.toString(context, change.newMemberLabelAccess); + if (editorIsYou) { + updates.add(updateDescription(context.getString(R.string.MessageRecord_you_changed_who_can_add_member_labels_to_s, accessLevel), Glyph.MEGAPHONE)); + } else { + updates.add(updateDescription(R.string.MessageRecord_s_changed_who_can_add_member_labels_to_s, change.editorServiceIdBytes, accessLevel, Glyph.MEGAPHONE)); + } + } + } + + private void describeUnknownEditorNewMemberLabelAccess(@NonNull DecryptedGroupChange change, @NonNull List updates) { + if (change.newMemberLabelAccess != AccessControl.AccessRequired.UNKNOWN) { + String accessLevel = GV2AccessLevelUtil.toString(context, change.newMemberLabelAccess); + updates.add(updateDescription(context.getString(R.string.MessageRecord_unknown_admin_changed_who_can_add_member_labels_to_s, accessLevel), Glyph.MEGAPHONE)); + } + } + private void describeNewGroupInviteLinkAccess(@Nullable DecryptedGroup previousGroupState, @NonNull DecryptedGroupChange change, @NonNull List updates) diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt index 4484cc2373..abec4b15db 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt @@ -60,6 +60,7 @@ import org.thoughtcrime.securesms.notifications.NotificationIds import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.util.RemoteConfig +import org.thoughtcrime.securesms.util.SignalTrace import org.thoughtcrime.securesms.util.asChain import org.whispersystems.signalservice.api.InvalidMessageStructureException import org.whispersystems.signalservice.api.crypto.ContentHint @@ -73,7 +74,6 @@ import org.whispersystems.signalservice.internal.push.Content import org.whispersystems.signalservice.internal.push.Envelope import org.whispersystems.signalservice.internal.push.PniSignatureMessage import org.whispersystems.signalservice.internal.util.Util -import org.thoughtcrime.securesms.util.SignalTrace import java.util.Optional import kotlin.time.Duration.Companion.nanoseconds import kotlin.time.DurationUnit diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e42ca83d60..41101658d0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2022,6 +2022,13 @@ %1$s changed who can edit group membership to \"%2$s\". Who can edit group membership has been changed to \"%1$s\". + + You changed who can add member labels to \"%1$s\". + + %1$s changed who can add member labels to \"%2$s\". + + An admin changed who can add member labels to \"%1$s\". + You changed the group settings to allow all members to send messages. You changed the group settings to only allow admins to send messages. diff --git a/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.kt b/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.kt index 7c6dc2dc04..b33d232291 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.kt @@ -824,6 +824,34 @@ class GroupsV2UpdateMessageProducerTest { assertEquals(listOf("Who can edit group membership has been changed to \"Only admins\"."), describeChange(change)) } + // member label access change + @Test + fun member_changes_member_label_access() { + val change = ChangeBuilder.changeBy(bob) + .memberLabelAccess(MEMBER) + .build() + + assertEquals(listOf("Bob changed who can add member labels to \"All members\"."), describeChange(change)) + } + + @Test + fun you_changed_member_label_access() { + val change = ChangeBuilder.changeBy(you) + .memberLabelAccess(ADMINISTRATOR) + .build() + + assertEquals(listOf("You changed who can add member labels to \"Only admins\"."), describeChange(change)) + } + + @Test + fun unknown_changed_member_label_access() { + val change = ChangeBuilder.changeByUnknown() + .memberLabelAccess(ADMINISTRATOR) + .build() + + assertEquals(listOf("An admin changed who can add member labels to \"Only admins\"."), describeChange(change)) + } + // Group link access change @Test fun you_changed_group_link_access_to_any() { diff --git a/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ChangeBuilder.java b/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ChangeBuilder.java index 69d79175dc..1e7993df4e 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ChangeBuilder.java +++ b/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ChangeBuilder.java @@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.groups.v2; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.signal.core.models.ServiceId.ACI; +import org.signal.core.util.Util; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.storageservice.storage.protos.groups.AccessControl; @@ -16,8 +18,6 @@ import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMem import org.signal.storageservice.storage.protos.groups.local.DecryptedRequestingMember; import org.signal.storageservice.storage.protos.groups.local.DecryptedString; import org.signal.storageservice.storage.protos.groups.local.DecryptedTimer; -import org.signal.core.util.Util; -import org.signal.core.models.ServiceId.ACI; import kotlin.collections.CollectionsKt; import okio.ByteString; @@ -128,6 +128,11 @@ public final class ChangeBuilder { return this; } + public ChangeBuilder memberLabelAccess(@NonNull AccessControl.AccessRequired accessRequired) { + builder.newMemberLabelAccess(accessRequired); + return this; + } + public ChangeBuilder inviteLinkAccess(@NonNull AccessControl.AccessRequired accessRequired) { builder.newInviteLinkAccess(accessRequired); return this;