Convert GroupTable to kotlin.

Also required converting some tests to mockk.
This commit is contained in:
Greyson Parrelli
2023-01-01 23:05:00 -05:00
parent fecfd7cd78
commit 92b9fda6c7
55 changed files with 1756 additions and 1881 deletions
@@ -11,9 +11,9 @@ import org.signal.core.util.logging.Log
import org.signal.storageservice.protos.groups.local.DecryptedGroup import org.signal.storageservice.protos.groups.local.DecryptedGroup
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember import org.signal.storageservice.protos.groups.local.DecryptedPendingMember
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery
import org.thoughtcrime.securesms.database.GroupTable
import org.thoughtcrime.securesms.database.MediaTable import org.thoughtcrime.securesms.database.MediaTable
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.GroupRecord
import org.thoughtcrime.securesms.database.model.IdentityRecord import org.thoughtcrime.securesms.database.model.IdentityRecord
import org.thoughtcrime.securesms.database.model.StoryViewState import org.thoughtcrime.securesms.database.model.StoryViewState
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
@@ -70,7 +70,7 @@ class ConversationSettingsRepository(
fun isInternalRecipientDetailsEnabled(): Boolean = SignalStore.internalValues().recipientDetails() fun isInternalRecipientDetailsEnabled(): Boolean = SignalStore.internalValues().recipientDetails()
fun hasGroups(consumer: (Boolean) -> Unit) { fun hasGroups(consumer: (Boolean) -> Unit) {
SignalExecutors.BOUNDED.execute { consumer(SignalDatabase.groups.activeGroupCount > 0) } SignalExecutors.BOUNDED.execute { consumer(SignalDatabase.groups.getActiveGroupCount() > 0) }
} }
fun getIdentity(recipientId: RecipientId, consumer: (IdentityRecord?) -> Unit) { fun getIdentity(recipientId: RecipientId, consumer: (IdentityRecord?) -> Unit) {
@@ -91,7 +91,7 @@ class ConversationSettingsRepository(
.getPushGroupsContainingMember(recipientId) .getPushGroupsContainingMember(recipientId)
.asSequence() .asSequence()
.filter { it.members.contains(Recipient.self().id) } .filter { it.members.contains(Recipient.self().id) }
.map(GroupTable.GroupRecord::getRecipientId) .map(GroupRecord::recipientId)
.map(Recipient::resolved) .map(Recipient::resolved)
.sortedBy { gr -> gr.getDisplayName(context) } .sortedBy { gr -> gr.getDisplayName(context) }
.toList() .toList()
@@ -129,7 +129,7 @@ class ConversationSettingsRepository(
fun getGroupCapacity(groupId: GroupId, consumer: (GroupCapacityResult) -> Unit) { fun getGroupCapacity(groupId: GroupId, consumer: (GroupCapacityResult) -> Unit) {
SignalExecutors.BOUNDED.execute { SignalExecutors.BOUNDED.execute {
val groupRecord: GroupTable.GroupRecord = SignalDatabase.groups.getGroup(groupId).get() val groupRecord: GroupRecord = SignalDatabase.groups.getGroup(groupId).get()
consumer( consumer(
if (groupRecord.isV2Group) { if (groupRecord.isV2Group) {
val decryptedGroup: DecryptedGroup = groupRecord.requireV2GroupProperties().decryptedGroup val decryptedGroup: DecryptedGroup = groupRecord.requireV2GroupProperties().decryptedGroup
@@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.RecipientTable; import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.ThreadTable; import org.thoughtcrime.securesms.database.ThreadTable;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.phonenumbers.NumberUtil; import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
@@ -221,11 +222,11 @@ public class ContactsCursorLoader extends AbstractContactsCursorLoader {
} }
private Cursor getGroupsCursor() { private Cursor getGroupsCursor() {
MatrixCursor groupContacts = ContactsCursorRows.createMatrixCursor(); MatrixCursor groupContacts = ContactsCursorRows.createMatrixCursor();
Map<RecipientId, GroupTable.GroupRecord> groups = new LinkedHashMap<>(); Map<RecipientId, GroupRecord> groups = new LinkedHashMap<>();
try (GroupTable.Reader reader = SignalDatabase.groups().queryGroupsByTitle(getFilter(), flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS), hideGroupsV1(mode), !smsEnabled(mode))) { try (GroupTable.Reader reader = SignalDatabase.groups().queryGroupsByTitle(getFilter(), flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS), hideGroupsV1(mode), !smsEnabled(mode))) {
GroupTable.GroupRecord groupRecord; GroupRecord groupRecord;
while ((groupRecord = reader.getNext()) != null) { while ((groupRecord = reader.getNext()) != null) {
groups.put(groupRecord.getRecipientId(), groupRecord); groups.put(groupRecord.getRecipientId(), groupRecord);
} }
@@ -240,14 +241,14 @@ public class ContactsCursorLoader extends AbstractContactsCursorLoader {
} }
try (GroupTable.Reader reader = SignalDatabase.groups().queryGroupsByMembership(filteredContacts, flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS), hideGroupsV1(mode), !smsEnabled(mode))) { try (GroupTable.Reader reader = SignalDatabase.groups().queryGroupsByMembership(filteredContacts, flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS), hideGroupsV1(mode), !smsEnabled(mode))) {
GroupTable.GroupRecord groupRecord; GroupRecord groupRecord;
while ((groupRecord = reader.getNext()) != null) { while ((groupRecord = reader.getNext()) != null) {
groups.put(groupRecord.getRecipientId(), groupRecord); groups.put(groupRecord.getRecipientId(), groupRecord);
} }
} }
} }
for (GroupTable.GroupRecord groupRecord : groups.values()) { for (GroupRecord groupRecord : groups.values()) {
groupContacts.addRow(ContactsCursorRows.forGroup(groupRecord)); groupContacts.addRow(ContactsCursorRows.forGroup(groupRecord));
} }
@@ -8,7 +8,7 @@ import android.provider.ContactsContract;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.whispersystems.signalservice.api.util.OptionalUtil; import org.whispersystems.signalservice.api.util.OptionalUtil;
@@ -74,7 +74,7 @@ public final class ContactsCursorRows {
/** /**
* Create a row for a contacts cursor based off the given group record. * Create a row for a contacts cursor based off the given group record.
*/ */
public static @NonNull Object[] forGroup(@NonNull GroupTable.GroupRecord groupRecord) { public static @NonNull Object[] forGroup(@NonNull GroupRecord groupRecord) {
return new Object[]{groupRecord.getRecipientId().serialize(), return new Object[]{groupRecord.getRecipientId().serialize(),
groupRecord.getTitle(), groupRecord.getTitle(),
groupRecord.getId(), groupRecord.getId(),
@@ -10,6 +10,7 @@ import androidx.annotation.Nullable;
import org.signal.core.util.Conversions; import org.signal.core.util.Conversions;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.profiles.AvatarHelper;
@@ -30,8 +31,8 @@ public final class GroupRecordContactPhoto implements ContactPhoto {
@Override @Override
public InputStream openInputStream(Context context) throws IOException { public InputStream openInputStream(Context context) throws IOException {
GroupTable groupDatabase = SignalDatabase.groups(); GroupTable groupDatabase = SignalDatabase.groups();
Optional<GroupTable.GroupRecord> groupRecord = groupDatabase.getGroup(groupId); Optional<GroupRecord> groupRecord = groupDatabase.getGroup(groupId);
if (!groupRecord.isPresent() || !AvatarHelper.hasAvatar(context, groupRecord.get().getRecipientId())) { if (!groupRecord.isPresent() || !AvatarHelper.hasAvatar(context, groupRecord.get().getRecipientId())) {
throw new IOException("No avatar for group: " + groupId); throw new IOException("No avatar for group: " + groupId);
@@ -6,8 +6,8 @@ import org.thoughtcrime.securesms.contacts.paged.collections.ContactSearchCollec
import org.thoughtcrime.securesms.contacts.paged.collections.ContactSearchIterator import org.thoughtcrime.securesms.contacts.paged.collections.ContactSearchIterator
import org.thoughtcrime.securesms.contacts.paged.collections.CursorSearchIterator import org.thoughtcrime.securesms.contacts.paged.collections.CursorSearchIterator
import org.thoughtcrime.securesms.contacts.paged.collections.StoriesSearchCollection import org.thoughtcrime.securesms.contacts.paged.collections.StoriesSearchCollection
import org.thoughtcrime.securesms.database.GroupTable.GroupRecord
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
import org.thoughtcrime.securesms.database.model.GroupRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.StorySend import org.thoughtcrime.securesms.keyvalue.StorySend
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
@@ -8,10 +8,10 @@ import org.thoughtcrime.securesms.contacts.ContactRepository
import org.thoughtcrime.securesms.contacts.paged.collections.ContactSearchIterator import org.thoughtcrime.securesms.contacts.paged.collections.ContactSearchIterator
import org.thoughtcrime.securesms.database.DistributionListTables import org.thoughtcrime.securesms.database.DistributionListTables
import org.thoughtcrime.securesms.database.GroupTable import org.thoughtcrime.securesms.database.GroupTable
import org.thoughtcrime.securesms.database.GroupTable.GroupRecord
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.ThreadTable import org.thoughtcrime.securesms.database.ThreadTable
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
import org.thoughtcrime.securesms.database.model.GroupRecord
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.keyvalue.StorySend import org.thoughtcrime.securesms.keyvalue.StorySend
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
@@ -104,7 +104,7 @@ open class ContactSearchPagedDataSourceRepository(
} }
open fun getGroupStories(): Set<ContactSearchData.Story> { open fun getGroupStories(): Set<ContactSearchData.Story> {
return SignalDatabase.groups.groupsToDisplayAsStories.map { return SignalDatabase.groups.getGroupsToDisplayAsStories().map {
val recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromGroupId(it)) val recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromGroupId(it))
ContactSearchData.Story(recipient, recipient.participantIds.size, DistributionListPrivacyMode.ALL) ContactSearchData.Story(recipient, recipient.participantIds.size, DistributionListPrivacyMode.ALL)
}.toSet() }.toSet()
@@ -16,7 +16,7 @@ import com.annimon.stream.Stream;
import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.GroupTable.GroupRecord; import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupChangeBusyException; import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
@@ -328,7 +328,8 @@ import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static org.thoughtcrime.securesms.database.GroupTable.GroupRecord;
import org.thoughtcrime.securesms.database.model.GroupRecord;
/** /**
* Fragment for displaying a message thread, as well as * Fragment for displaying a message thread, as well as
@@ -10,11 +10,11 @@ import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery; import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
import org.thoughtcrime.securesms.database.DatabaseObserver; import org.thoughtcrime.securesms.database.DatabaseObserver;
import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.database.RecipientTable; import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.ThreadTable; import org.thoughtcrime.securesms.database.ThreadTable;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.MultiDeviceViewedUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceViewedUpdateJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
@@ -84,7 +84,7 @@ class ConversationRepository {
boolean isGroup = false; boolean isGroup = false;
boolean recipientIsKnownOrHasGroupsInCommon = false; boolean recipientIsKnownOrHasGroupsInCommon = false;
if (conversationRecipient.isGroup()) { if (conversationRecipient.isGroup()) {
Optional<GroupTable.GroupRecord> group = SignalDatabase.groups().getGroup(conversationRecipient.getId()); Optional<GroupRecord> group = SignalDatabase.groups().getGroup(conversationRecipient.getId());
if (group.isPresent()) { if (group.isPresent()) {
List<Recipient> recipients = Recipient.resolvedList(group.get().getMembers()); List<Recipient> recipients = Recipient.resolvedList(group.get().getMembers());
for (Recipient recipient : recipients) { for (Recipient recipient : recipients) {
@@ -15,8 +15,8 @@ import androidx.fragment.app.FragmentManager;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.ParcelableGroupId; import org.thoughtcrime.securesms.groups.ParcelableGroupId;
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView; import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
@@ -94,7 +94,7 @@ public final class ShowAdminsBottomSheetDialog extends BottomSheetDialogFragment
private static @NonNull List<Recipient> getAdmins(@NonNull Context context, @NonNull GroupId groupId) { private static @NonNull List<Recipient> getAdmins(@NonNull Context context, @NonNull GroupId groupId) {
return SignalDatabase.groups() return SignalDatabase.groups()
.getGroup(groupId) .getGroup(groupId)
.map(GroupTable.GroupRecord::getAdmins) .map(GroupRecord::getAdmins)
.orElse(Collections.emptyList()); .orElse(Collections.emptyList());
} }
} }
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -54,6 +54,7 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.documents.NetworkFailureSet; import org.thoughtcrime.securesms.database.documents.NetworkFailureSet;
import org.thoughtcrime.securesms.database.model.DisplayRecord; import org.thoughtcrime.securesms.database.model.DisplayRecord;
import org.thoughtcrime.securesms.database.model.GroupCallUpdateDetailsUtil; import org.thoughtcrime.securesms.database.model.GroupCallUpdateDetailsUtil;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.Mention; import org.thoughtcrime.securesms.database.model.Mention;
import org.thoughtcrime.securesms.database.model.MessageExportStatus; import org.thoughtcrime.securesms.database.model.MessageExportStatus;
@@ -941,9 +942,9 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie
} }
public void insertProfileNameChangeMessages(@NonNull Recipient recipient, @NonNull String newProfileName, @NonNull String previousProfileName) { public void insertProfileNameChangeMessages(@NonNull Recipient recipient, @NonNull String newProfileName, @NonNull String previousProfileName) {
ThreadTable threadTable = SignalDatabase.threads(); ThreadTable threadTable = SignalDatabase.threads();
List<GroupTable.GroupRecord> groupRecords = SignalDatabase.groups().getGroupsContainingMember(recipient.getId(), false); List<GroupRecord> groupRecords = SignalDatabase.groups().getGroupsContainingMember(recipient.getId(), false);
List<Long> threadIdsToUpdate = new LinkedList<>(); List<Long> threadIdsToUpdate = new LinkedList<>();
byte[] profileChangeDetails = ProfileChangeDetails.newBuilder() byte[] profileChangeDetails = ProfileChangeDetails.newBuilder()
.setProfileNameChange(ProfileChangeDetails.StringChange.newBuilder() .setProfileNameChange(ProfileChangeDetails.StringChange.newBuilder()
@@ -959,7 +960,7 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie
try { try {
threadIdsToUpdate.add(threadTable.getThreadIdFor(recipient.getId())); threadIdsToUpdate.add(threadTable.getThreadIdFor(recipient.getId()));
for (GroupTable.GroupRecord groupRecord : groupRecords) { for (GroupRecord groupRecord : groupRecords) {
if (groupRecord.isActive()) { if (groupRecord.isActive()) {
threadIdsToUpdate.add(threadTable.getThreadIdFor(groupRecord.getRecipientId())); threadIdsToUpdate.add(threadTable.getThreadIdFor(groupRecord.getRecipientId()));
} }
@@ -1032,16 +1033,16 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie
} }
public void insertNumberChangeMessages(@NonNull RecipientId recipientId) { public void insertNumberChangeMessages(@NonNull RecipientId recipientId) {
ThreadTable threadTable = SignalDatabase.threads(); ThreadTable threadTable = SignalDatabase.threads();
List<GroupTable.GroupRecord> groupRecords = SignalDatabase.groups().getGroupsContainingMember(recipientId, false); List<GroupRecord> groupRecords = SignalDatabase.groups().getGroupsContainingMember(recipientId, false);
List<Long> threadIdsToUpdate = new LinkedList<>(); List<Long> threadIdsToUpdate = new LinkedList<>();
SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); SQLiteDatabase db = databaseHelper.getSignalWritableDatabase();
db.beginTransaction(); db.beginTransaction();
try { try {
threadIdsToUpdate.add(threadTable.getThreadIdFor(recipientId)); threadIdsToUpdate.add(threadTable.getThreadIdFor(recipientId));
for (GroupTable.GroupRecord groupRecord : groupRecords) { for (GroupRecord groupRecord : groupRecords) {
if (groupRecord.isActive()) { if (groupRecord.isActive()) {
threadIdsToUpdate.add(threadTable.getThreadIdFor(groupRecord.getRecipientId())); threadIdsToUpdate.add(threadTable.getThreadIdFor(groupRecord.getRecipientId()));
} }
@@ -1186,7 +1186,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
} }
} }
for (id in groups.allGroupV2Ids) { for (id in groups.getAllGroupV2Ids()) {
val recipient = Recipient.externalGroupExact(id!!) val recipient = Recipient.externalGroupExact(id!!)
val recipientId = recipient.id val recipientId = recipient.id
val existing: RecipientRecord = getRecordForSync(recipientId) ?: throw AssertionError() val existing: RecipientRecord = getRecordForSync(recipientId) ?: throw AssertionError()
@@ -1670,7 +1670,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
val recipientSettings = recipients.getRecord(context, cursor, RECIPIENT_ID) val recipientSettings = recipients.getRecord(context, cursor, RECIPIENT_ID)
val recipient: Recipient = if (recipientSettings.groupId != null) { val recipient: Recipient = if (recipientSettings.groupId != null) {
GroupTable.Reader(cursor).current?.let { group -> GroupTable.Reader(cursor).getCurrent()?.let { group ->
val details = RecipientDetails( val details = RecipientDetails(
group.title, group.title,
null, null,
@@ -0,0 +1,163 @@
package org.thoughtcrime.securesms.database.model
import androidx.annotation.WorkerThread
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.signal.storageservice.protos.groups.AccessControl
import org.signal.storageservice.protos.groups.local.EnabledState
import org.thoughtcrime.securesms.database.GroupTable
import org.thoughtcrime.securesms.groups.GroupAccessControl
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil
import org.whispersystems.signalservice.api.push.DistributionId
import java.lang.AssertionError
import java.util.Optional
class GroupRecord(
val id: GroupId,
val recipientId: RecipientId,
val title: String?,
serializedMembers: String?,
serializedUnmigratedV1Members: String?,
val avatarId: Long,
val avatarKey: ByteArray?,
val avatarContentType: String?,
val relay: String?,
val isActive: Boolean,
val avatarDigest: ByteArray?,
val isMms: Boolean,
groupMasterKeyBytes: ByteArray?,
groupRevision: Int,
decryptedGroupBytes: ByteArray?,
val distributionId: DistributionId?,
val lastForceUpdateTimestamp: Long
) {
val members: List<RecipientId> by lazy {
if (serializedMembers.isNullOrEmpty()) {
emptyList()
} else {
RecipientId.fromSerializedList(serializedMembers)
}
}
/** V1 members that were lost during the V1->V2 migration */
val unmigratedV1Members: List<RecipientId> by lazy {
if (serializedUnmigratedV1Members.isNullOrEmpty()) {
emptyList()
} else {
RecipientId.fromSerializedList(serializedUnmigratedV1Members)
}
}
private val v2GroupProperties: GroupTable.V2GroupProperties? by lazy {
if (groupMasterKeyBytes != null && decryptedGroupBytes != null) {
val groupMasterKey = GroupMasterKey(groupMasterKeyBytes)
GroupTable.V2GroupProperties(groupMasterKey, groupRevision, decryptedGroupBytes)
} else {
null
}
}
val description: String
get() = v2GroupProperties?.decryptedGroup?.description ?: ""
val isAnnouncementGroup: Boolean
get() = v2GroupProperties?.decryptedGroup?.isAnnouncementGroup == EnabledState.ENABLED
val isV1Group: Boolean
get() = !isMms && !isV2Group
val isV2Group: Boolean
get() = v2GroupProperties != null
@get:WorkerThread
val admins: List<Recipient>
get() {
return if (v2GroupProperties != null) {
val resolved = members.map { Recipient.resolved(it) }
v2GroupProperties!!.getAdmins(resolved)
} else {
emptyList()
}
}
/** Who is allowed to add to the membership of this group. */
val membershipAdditionAccessControl: GroupAccessControl
get() {
return if (isV2Group) {
if (requireV2GroupProperties().decryptedGroup.accessControl.members == AccessControl.AccessRequired.MEMBER) {
GroupAccessControl.ALL_MEMBERS
} else {
GroupAccessControl.ONLY_ADMINS
}
} else if (isV1Group) {
GroupAccessControl.NO_ONE
} else if (id.isV1) {
GroupAccessControl.ALL_MEMBERS
} else {
GroupAccessControl.ONLY_ADMINS
}
}
/** Who is allowed to modify the attributes of this group, name/avatar/timer etc. */
val attributesAccessControl: GroupAccessControl
get() {
return if (isV2Group) {
if (requireV2GroupProperties().decryptedGroup.accessControl.attributes == AccessControl.AccessRequired.MEMBER) {
GroupAccessControl.ALL_MEMBERS
} else {
GroupAccessControl.ONLY_ADMINS
}
} else if (isV1Group) {
GroupAccessControl.NO_ONE
} else {
GroupAccessControl.ALL_MEMBERS
}
}
fun hasAvatar(): Boolean {
return avatarId != 0L
}
fun requireV2GroupProperties(): GroupTable.V2GroupProperties {
return v2GroupProperties ?: throw AssertionError()
}
fun isAdmin(recipient: Recipient): Boolean {
return isV2Group && requireV2GroupProperties().isAdmin(recipient)
}
fun memberLevel(recipient: Recipient): GroupTable.MemberLevel {
return if (isV2Group) {
val memberLevel = requireV2GroupProperties().memberLevel(recipient.serviceId)
if (recipient.isSelf && memberLevel == GroupTable.MemberLevel.NOT_A_MEMBER) {
requireV2GroupProperties().memberLevel(Optional.ofNullable(SignalStore.account().pni))
} else {
memberLevel
}
} else if (isMms && recipient.isSelf) {
GroupTable.MemberLevel.FULL_MEMBER
} else if (members.contains(recipient.id)) {
GroupTable.MemberLevel.FULL_MEMBER
} else {
GroupTable.MemberLevel.NOT_A_MEMBER
}
}
/**
* Whether or not the recipient is a pending member.
*/
fun isPendingMember(recipient: Recipient): Boolean {
if (isV2Group) {
val serviceId = recipient.serviceId
if (serviceId.isPresent) {
return DecryptedGroupUtil.findPendingByUuid(requireV2GroupProperties().decryptedGroup.pendingMembersList, serviceId.get().uuid())
.isPresent
}
}
return false
}
}
@@ -10,6 +10,7 @@ import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
@@ -78,7 +79,7 @@ class DeleteAccountRepository {
int groupsLeft = 0; int groupsLeft = 0;
try (GroupTable.Reader groups = SignalDatabase.groups().getGroups()) { try (GroupTable.Reader groups = SignalDatabase.groups().getGroups()) {
GroupTable.GroupRecord groupRecord = groups.getNext(); GroupRecord groupRecord = groups.getNext();
onDeleteAccountEvent.accept(new DeleteAccountEvent.LeaveGroupsProgress(groups.getCount(), 0)); onDeleteAccountEvent.accept(new DeleteAccountEvent.LeaveGroupsProgress(groups.getCount(), 0));
Log.i(TAG, "deleteAccount: found " + groups.getCount() + " groups to leave."); Log.i(TAG, "deleteAccount: found " + groups.getCount() + " groups to leave.");
@@ -15,6 +15,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo; import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl; import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl;
import org.thoughtcrime.securesms.groups.v2.GroupLinkPassword; import org.thoughtcrime.securesms.groups.v2.GroupLinkPassword;
import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.profiles.AvatarHelper;
@@ -402,14 +403,14 @@ public final class GroupManager {
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException, GroupChangeBusyException, MembershipNotSuitableForV2Exception throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException, GroupChangeBusyException, MembershipNotSuitableForV2Exception
{ {
if (groupId.isV2()) { if (groupId.isV2()) {
GroupTable.GroupRecord groupRecord = SignalDatabase.groups().requireGroup(groupId); GroupRecord groupRecord = SignalDatabase.groups().requireGroup(groupId);
try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) { try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) {
return editor.addMembers(newMembers, groupRecord.requireV2GroupProperties().getBannedMembers()); return editor.addMembers(newMembers, groupRecord.requireV2GroupProperties().getBannedMembers());
} }
} else { } else {
GroupTable.GroupRecord groupRecord = SignalDatabase.groups().requireGroup(groupId); GroupRecord groupRecord = SignalDatabase.groups().requireGroup(groupId);
List<RecipientId> members = groupRecord.getMembers(); List<RecipientId> members = groupRecord.getMembers();
byte[] avatar = groupRecord.hasAvatar() ? AvatarHelper.getAvatarBytes(context, groupRecord.getRecipientId()) : null; byte[] avatar = groupRecord.hasAvatar() ? AvatarHelper.getAvatarBytes(context, groupRecord.getRecipientId()) : null;
Set<RecipientId> recipientIds = new HashSet<>(members); Set<RecipientId> recipientIds = new HashSet<>(members);
int originalSize = recipientIds.size(); int originalSize = recipientIds.size();
@@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.ThreadTable; import org.thoughtcrime.securesms.database.ThreadTable;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context; import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.v2.GroupCandidateHelper; import org.thoughtcrime.securesms.groups.v2.GroupCandidateHelper;
@@ -167,8 +168,8 @@ final class GroupManagerV2 {
@WorkerThread @WorkerThread
@NonNull Map<UUID, UuidCiphertext> getUuidCipherTexts(@NonNull GroupId.V2 groupId) { @NonNull Map<UUID, UuidCiphertext> getUuidCipherTexts(@NonNull GroupId.V2 groupId) {
GroupTable.GroupRecord groupRecord = SignalDatabase.groups().requireGroup(groupId); GroupRecord groupRecord = SignalDatabase.groups().requireGroup(groupId);
GroupTable.V2GroupProperties v2GroupProperties = groupRecord.requireV2GroupProperties(); GroupTable.V2GroupProperties v2GroupProperties = groupRecord.requireV2GroupProperties();
GroupMasterKey groupMasterKey = v2GroupProperties.getGroupMasterKey(); GroupMasterKey groupMasterKey = v2GroupProperties.getGroupMasterKey();
ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher(GroupSecretParams.deriveFromMasterKey(groupMasterKey)); ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher(GroupSecretParams.deriveFromMasterKey(groupMasterKey));
List<Recipient> recipients = v2GroupProperties.getMemberRecipients(GroupTable.MemberSet.FULL_MEMBERS_INCLUDING_SELF); List<Recipient> recipients = v2GroupProperties.getMemberRecipients(GroupTable.MemberSet.FULL_MEMBERS_INCLUDING_SELF);
@@ -251,9 +252,9 @@ final class GroupManagerV2 {
throws IOException, MembershipNotSuitableForV2Exception, GroupAlreadyExistsException, GroupChangeFailedException throws IOException, MembershipNotSuitableForV2Exception, GroupAlreadyExistsException, GroupChangeFailedException
{ {
GroupMasterKey groupMasterKey = groupIdV1.deriveV2MigrationMasterKey(); GroupMasterKey groupMasterKey = groupIdV1.deriveV2MigrationMasterKey();
GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
GroupTable.GroupRecord groupRecord = groupDatabase.requireGroup(groupIdV1); GroupRecord groupRecord = groupDatabase.requireGroup(groupIdV1);
String name = Util.emptyIfNull(groupRecord.getTitle()); String name = Util.emptyIfNull(groupRecord.getTitle());
byte[] avatar = groupRecord.hasAvatar() ? AvatarHelper.getAvatarBytes(context, groupRecord.getRecipientId()) : null; byte[] avatar = groupRecord.hasAvatar() ? AvatarHelper.getAvatarBytes(context, groupRecord.getRecipientId()) : null;
int messageTimer = Recipient.resolved(groupRecord.getRecipientId()).getExpiresInSeconds(); int messageTimer = Recipient.resolved(groupRecord.getRecipientId()).getExpiresInSeconds();
Set<RecipientId> memberIds = Stream.of(members) Set<RecipientId> memberIds = Stream.of(members)
@@ -327,7 +328,7 @@ final class GroupManagerV2 {
GroupEditor(@NonNull GroupId.V2 groupId, @NonNull Closeable lock) { GroupEditor(@NonNull GroupId.V2 groupId, @NonNull Closeable lock) {
super(lock); super(lock);
GroupTable.GroupRecord groupRecord = groupDatabase.requireGroup(groupId); GroupRecord groupRecord = groupDatabase.requireGroup(groupId);
this.groupId = groupId; this.groupId = groupId;
this.v2GroupProperties = groupRecord.requireV2GroupProperties(); this.v2GroupProperties = groupRecord.requireV2GroupProperties();
@@ -455,8 +456,8 @@ final class GroupManagerV2 {
void leaveGroup() void leaveGroup()
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException
{ {
GroupTable.GroupRecord groupRecord = groupDatabase.requireGroup(groupId); GroupRecord groupRecord = groupDatabase.requireGroup(groupId);
DecryptedGroup decryptedGroup = groupRecord.requireV2GroupProperties().getDecryptedGroup(); DecryptedGroup decryptedGroup = groupRecord.requireV2GroupProperties().getDecryptedGroup();
Optional<DecryptedMember> selfMember = DecryptedGroupUtil.findMemberByUuid(decryptedGroup.getMembersList(), selfAci.uuid()); Optional<DecryptedMember> selfMember = DecryptedGroupUtil.findMemberByUuid(decryptedGroup.getMembersList(), selfAci.uuid());
Optional<DecryptedPendingMember> aciPendingMember = DecryptedGroupUtil.findPendingByUuid(decryptedGroup.getPendingMembersList(), selfAci.uuid()); Optional<DecryptedPendingMember> aciPendingMember = DecryptedGroupUtil.findPendingByUuid(decryptedGroup.getPendingMembersList(), selfAci.uuid());
Optional<DecryptedPendingMember> pniPendingMember = DecryptedGroupUtil.findPendingByUuid(decryptedGroup.getPendingMembersList(), selfPni.uuid()); Optional<DecryptedPendingMember> pniPendingMember = DecryptedGroupUtil.findPendingByUuid(decryptedGroup.getPendingMembersList(), selfPni.uuid());
@@ -728,7 +729,7 @@ final class GroupManagerV2 {
private GroupManager.GroupActionResult commitChange(@NonNull GroupChange.Actions.Builder change, boolean allowWhenBlocked, boolean sendToMembers) private GroupManager.GroupActionResult commitChange(@NonNull GroupChange.Actions.Builder change, boolean allowWhenBlocked, boolean sendToMembers)
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
{ {
final GroupTable.GroupRecord groupRecord = groupDatabase.requireGroup(groupId); final GroupRecord groupRecord = groupDatabase.requireGroup(groupId);
final GroupTable.V2GroupProperties v2GroupProperties = groupRecord.requireV2GroupProperties(); final GroupTable.V2GroupProperties v2GroupProperties = groupRecord.requireV2GroupProperties();
final int nextRevision = v2GroupProperties.getGroupRevision() + 1; final int nextRevision = v2GroupProperties.getGroupRevision() + 1;
final GroupChange.Actions changeActions = change.setRevision(nextRevision).build(); final GroupChange.Actions changeActions = change.setRevision(nextRevision).build();
@@ -923,7 +924,7 @@ final class GroupManagerV2 {
alreadyAMember = true; alreadyAMember = true;
} }
Optional<GroupTable.GroupRecord> unmigratedV1Group = groupDatabase.getGroupV1ByExpectedV2(groupId); Optional<GroupRecord> unmigratedV1Group = groupDatabase.getGroupV1ByExpectedV2(groupId);
if (unmigratedV1Group.isPresent()) { if (unmigratedV1Group.isPresent()) {
Log.i(TAG, "Group link was for a migrated V1 group we know about! Migrating it and using that as the base."); Log.i(TAG, "Group link was for a migrated V1 group we know about! Migrating it and using that as the base.");
@@ -932,7 +933,7 @@ final class GroupManagerV2 {
DecryptedGroup decryptedGroup = createPlaceholderGroup(joinInfo, requestToJoin); DecryptedGroup decryptedGroup = createPlaceholderGroup(joinInfo, requestToJoin);
Optional<GroupTable.GroupRecord> group = groupDatabase.getGroup(groupId); Optional<GroupRecord> group = groupDatabase.getGroup(groupId);
if (group.isPresent()) { if (group.isPresent()) {
Log.i(TAG, "Group already present locally"); Log.i(TAG, "Group already present locally");
@@ -19,6 +19,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry; import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl; import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl;
@@ -48,7 +49,7 @@ public final class LiveGroup {
private final GroupTable groupDatabase; private final GroupTable groupDatabase;
private final LiveData<Recipient> recipient; private final LiveData<Recipient> recipient;
private final LiveData<GroupTable.GroupRecord> groupRecord; private final LiveData<GroupRecord> groupRecord;
private final LiveData<List<GroupMemberEntry.FullMember>> fullMembers; private final LiveData<List<GroupMemberEntry.FullMember>> fullMembers;
private final LiveData<List<GroupMemberEntry.RequestingMember>> requestingMembers; private final LiveData<List<GroupMemberEntry.RequestingMember>> requestingMembers;
private final LiveData<GroupLinkUrlAndStatus> groupLink; private final LiveData<GroupLinkUrlAndStatus> groupLink;
@@ -64,7 +65,7 @@ public final class LiveGroup {
this.requestingMembers = mapToRequestingMembers(this.groupRecord); this.requestingMembers = mapToRequestingMembers(this.groupRecord);
if (groupId.isV2()) { if (groupId.isV2()) {
LiveData<GroupTable.V2GroupProperties> v2Properties = Transformations.map(this.groupRecord, GroupTable.GroupRecord::requireV2GroupProperties); LiveData<GroupTable.V2GroupProperties> v2Properties = Transformations.map(this.groupRecord, GroupRecord::requireV2GroupProperties);
this.groupLink = Transformations.map(v2Properties, g -> { this.groupLink = Transformations.map(v2Properties, g -> {
DecryptedGroup group = g.getDecryptedGroup(); DecryptedGroup group = g.getDecryptedGroup();
AccessControl.AccessRequired addFromInviteLink = group.getAccessControl().getAddFromInviteLink(); AccessControl.AccessRequired addFromInviteLink = group.getAccessControl().getAddFromInviteLink();
@@ -87,7 +88,7 @@ public final class LiveGroup {
SignalExecutors.BOUNDED.execute(() -> liveRecipient.postValue(Recipient.externalGroupExact(groupId).live())); SignalExecutors.BOUNDED.execute(() -> liveRecipient.postValue(Recipient.externalGroupExact(groupId).live()));
} }
protected static LiveData<List<GroupMemberEntry.FullMember>> mapToFullMembers(@NonNull LiveData<GroupTable.GroupRecord> groupRecord) { protected static LiveData<List<GroupMemberEntry.FullMember>> mapToFullMembers(@NonNull LiveData<GroupRecord> groupRecord) {
return LiveDataUtil.mapAsync(groupRecord, return LiveDataUtil.mapAsync(groupRecord,
g -> Stream.of(g.getMembers()) g -> Stream.of(g.getMembers())
.map(m -> { .map(m -> {
@@ -98,7 +99,7 @@ public final class LiveGroup {
.toList()); .toList());
} }
protected static LiveData<List<GroupMemberEntry.RequestingMember>> mapToRequestingMembers(@NonNull LiveData<GroupTable.GroupRecord> groupRecord) { protected static LiveData<List<GroupMemberEntry.RequestingMember>> mapToRequestingMembers(@NonNull LiveData<GroupRecord> groupRecord) {
return LiveDataUtil.mapAsync(groupRecord, return LiveDataUtil.mapAsync(groupRecord,
g -> { g -> {
if (!g.isV2Group()) { if (!g.isV2Group()) {
@@ -128,11 +129,11 @@ public final class LiveGroup {
} }
public LiveData<String> getDescription() { public LiveData<String> getDescription() {
return Transformations.map(groupRecord, GroupTable.GroupRecord::getDescription); return Transformations.map(groupRecord, GroupRecord::getDescription);
} }
public LiveData<Boolean> isAnnouncementGroup() { public LiveData<Boolean> isAnnouncementGroup() {
return Transformations.map(groupRecord, GroupTable.GroupRecord::isAnnouncementGroup); return Transformations.map(groupRecord, GroupRecord::isAnnouncementGroup);
} }
public LiveData<Recipient> getGroupRecipient() { public LiveData<Recipient> getGroupRecipient() {
@@ -148,7 +149,7 @@ public final class LiveGroup {
} }
public LiveData<Boolean> isActive() { public LiveData<Boolean> isActive() {
return Transformations.map(groupRecord, GroupTable.GroupRecord::isActive); return Transformations.map(groupRecord, GroupRecord::isActive);
} }
public LiveData<Boolean> getRecipientIsAdmin(@NonNull RecipientId recipientId) { public LiveData<Boolean> getRecipientIsAdmin(@NonNull RecipientId recipientId) {
@@ -171,11 +172,11 @@ public final class LiveGroup {
} }
public LiveData<GroupAccessControl> getMembershipAdditionAccessControl() { public LiveData<GroupAccessControl> getMembershipAdditionAccessControl() {
return Transformations.map(groupRecord, GroupTable.GroupRecord::getMembershipAdditionAccessControl); return Transformations.map(groupRecord, GroupRecord::getMembershipAdditionAccessControl);
} }
public LiveData<GroupAccessControl> getAttributesAccessControl() { public LiveData<GroupAccessControl> getAttributesAccessControl() {
return Transformations.map(groupRecord, GroupTable.GroupRecord::getAttributesAccessControl); return Transformations.map(groupRecord, GroupRecord::getAttributesAccessControl);
} }
public LiveData<List<GroupMemberEntry.FullMember>> getNonAdminFullMembers() { public LiveData<List<GroupMemberEntry.FullMember>> getNonAdminFullMembers() {
@@ -14,6 +14,7 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.groups.GroupChangeException; import org.thoughtcrime.securesms.groups.GroupChangeException;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.GroupManager;
@@ -56,7 +57,7 @@ public final class LeaveGroupDialog {
SimpleTask.run(activity.getLifecycle(), () -> { SimpleTask.run(activity.getLifecycle(), () -> {
GroupTable.V2GroupProperties groupProperties = SignalDatabase.groups() GroupTable.V2GroupProperties groupProperties = SignalDatabase.groups()
.getGroup(groupId) .getGroup(groupId)
.map(GroupTable.GroupRecord::requireV2GroupProperties) .map(GroupRecord::requireV2GroupProperties)
.orElse(null); .orElse(null);
if (groupProperties != null && groupProperties.isAdmin(Recipient.self())) { if (groupProperties != null && groupProperties.isAdmin(Recipient.self())) {
@@ -20,7 +20,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember; import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
import org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval; import org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.GroupTable.GroupRecord; import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.database.RecipientTable; import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
@@ -5,7 +5,7 @@ import androidx.annotation.VisibleForTesting;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.GroupTable.GroupRecord; import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Data;
@@ -5,7 +5,7 @@ import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.InvalidMessageException;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.GroupTable.GroupRecord; import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
@@ -10,7 +10,7 @@ import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey; import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.libsignal.zkgroup.groups.GroupSecretParams; import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.GroupTable.GroupRecord; import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
@@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Data;
@@ -28,7 +28,7 @@ public final class ForceUpdateGroupV2Job extends BaseJob {
public static void enqueueIfNecessary(@NonNull GroupId.V2 groupId) { public static void enqueueIfNecessary(@NonNull GroupId.V2 groupId) {
SignalExecutors.BOUNDED.execute(() -> { SignalExecutors.BOUNDED.execute(() -> {
Optional<GroupTable.GroupRecord> group = SignalDatabase.groups().getGroup(groupId); Optional<GroupRecord> group = SignalDatabase.groups().getGroup(groupId);
if (group.isPresent() && if (group.isPresent() &&
group.get().isV2Group() && group.get().isV2Group() &&
group.get().getLastForceUpdateTimestamp() + FORCE_UPDATE_INTERVAL < System.currentTimeMillis() group.get().getLastForceUpdateTimestamp() + FORCE_UPDATE_INTERVAL < System.currentTimeMillis()
@@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.groups.GroupChangeBusyException; import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.GroupManager;
@@ -61,7 +61,7 @@ final class ForceUpdateGroupV2WorkerJob extends BaseJob {
@Override @Override
public void onRun() throws IOException, GroupNotAMemberException, GroupChangeBusyException { public void onRun() throws IOException, GroupNotAMemberException, GroupChangeBusyException {
Optional<GroupTable.GroupRecord> group = SignalDatabase.groups().getGroup(groupId); Optional<GroupRecord> group = SignalDatabase.groups().getGroup(groupId);
if (!group.isPresent()) { if (!group.isPresent()) {
Log.w(TAG, "Group not found"); Log.w(TAG, "Group not found");
@@ -8,8 +8,8 @@ import com.google.protobuf.ByteString;
import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.storageservice.protos.groups.local.DecryptedMember; import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupChangeBusyException; import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
import org.thoughtcrime.securesms.groups.GroupChangeFailedException; import org.thoughtcrime.securesms.groups.GroupChangeFailedException;
@@ -112,7 +112,7 @@ public final class GroupV2UpdateSelfProfileKeyJob extends BaseJob {
boolean foundMismatch = false; boolean foundMismatch = false;
for (GroupId.V2 id : SignalDatabase.groups().getAllGroupV2Ids()) { for (GroupId.V2 id : SignalDatabase.groups().getAllGroupV2Ids()) {
Optional<GroupTable.GroupRecord> group = SignalDatabase.groups().getGroup(id); Optional<GroupRecord> group = SignalDatabase.groups().getGroup(id);
if (!group.isPresent()) { if (!group.isPresent()) {
Log.w(TAG, "Group " + group + " no longer exists?"); Log.w(TAG, "Group " + group + " no longer exists?");
continue; continue;
@@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.conversation.colors.ChatColorsMapper;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.Job;
@@ -101,7 +102,7 @@ public class MultiDeviceGroupUpdateJob extends BaseJob {
DeviceGroupsOutputStream out = new DeviceGroupsOutputStream(new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1])); DeviceGroupsOutputStream out = new DeviceGroupsOutputStream(new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]));
boolean hasData = false; boolean hasData = false;
GroupTable.GroupRecord record; GroupRecord record;
while ((record = reader.getNext()) != null) { while ((record = reader.getNext()) != null) {
if (record.isV1Group()) { if (record.isV1Group()) {
@@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.model.MessageId; import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
@@ -252,7 +253,7 @@ public final class PushGroupSendJob extends PushSendJob {
.anyMatch(info -> info.getStatus() > GroupReceiptTable.STATUS_UNDELIVERED); .anyMatch(info -> info.getStatus() > GroupReceiptTable.STATUS_UNDELIVERED);
if (message.getStoryType().isStory()) { if (message.getStoryType().isStory()) {
Optional<GroupTable.GroupRecord> groupRecord = SignalDatabase.groups().getGroup(groupId); Optional<GroupRecord> groupRecord = SignalDatabase.groups().getGroup(groupId);
if (groupRecord.isPresent() && groupRecord.get().isAnnouncementGroup() && !groupRecord.get().isAdmin(Recipient.self())) { if (groupRecord.isPresent() && groupRecord.get().isAnnouncementGroup() && !groupRecord.get().isAdmin(Recipient.self())) {
throw new UndeliverableMessageException("Non-admins cannot send stories in announcement groups!"); throw new UndeliverableMessageException("Non-admins cannot send stories in announcement groups!");
@@ -301,7 +302,7 @@ public final class PushGroupSendJob extends PushSendJob {
throw new UndeliverableMessageException("Messages can no longer be sent to V1 groups!"); throw new UndeliverableMessageException("Messages can no longer be sent to V1 groups!");
} }
} else { } else {
Optional<GroupTable.GroupRecord> groupRecord = SignalDatabase.groups().getGroup(groupRecipient.requireGroupId()); Optional<GroupRecord> groupRecord = SignalDatabase.groups().getGroup(groupRecipient.requireGroupId());
if (groupRecord.isPresent() && groupRecord.get().isAnnouncementGroup() && !groupRecord.get().isAdmin(Recipient.self())) { if (groupRecord.isPresent() && groupRecord.get().isAnnouncementGroup() && !groupRecord.get().isAdmin(Recipient.self())) {
throw new UndeliverableMessageException("Non-admins cannot send messages in announcement groups!"); throw new UndeliverableMessageException("Non-admins cannot send messages in announcement groups!");
@@ -4,8 +4,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.groups.GroupChangeBusyException; import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.GroupManager;
@@ -76,7 +76,7 @@ final class RequestGroupV2InfoWorkerJob extends BaseJob {
Log.i(TAG, "Updating group to revision " + toRevision); Log.i(TAG, "Updating group to revision " + toRevision);
} }
Optional<GroupTable.GroupRecord> group = SignalDatabase.groups().getGroup(groupId); Optional<GroupRecord> group = SignalDatabase.groups().getGroup(groupId);
if (!group.isPresent()) { if (!group.isPresent()) {
Log.w(TAG, "Group not found"); Log.w(TAG, "Group not found");
@@ -11,7 +11,7 @@ import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.SignalProtocolAddress; import org.signal.libsignal.protocol.SignalProtocolAddress;
import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage; import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.GroupTable.GroupRecord; import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.DistributionListRecord; import org.thoughtcrime.securesms.database.model.DistributionListRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
@@ -23,8 +23,8 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.UriAttachment; import org.thoughtcrime.securesms.attachments.UriAttachment;
import org.thoughtcrime.securesms.database.AttachmentTable; import org.thoughtcrime.securesms.database.AttachmentTable;
import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.GroupManager;
@@ -282,14 +282,14 @@ public class LinkPreviewRepository {
} }
GroupMasterKey groupMasterKey = groupInviteLinkUrl.getGroupMasterKey(); GroupMasterKey groupMasterKey = groupInviteLinkUrl.getGroupMasterKey();
GroupId.V2 groupId = GroupId.v2(groupMasterKey); GroupId.V2 groupId = GroupId.v2(groupMasterKey);
Optional<GroupTable.GroupRecord> group = SignalDatabase.groups().getGroup(groupId); Optional<GroupRecord> group = SignalDatabase.groups().getGroup(groupId);
if (group.isPresent()) { if (group.isPresent()) {
Log.i(TAG, "Creating preview for locally available group"); Log.i(TAG, "Creating preview for locally available group");
GroupTable.GroupRecord groupRecord = group.get(); GroupRecord groupRecord = group.get();
String title = groupRecord.getTitle(); String title = groupRecord.getTitle();
int memberCount = groupRecord.getMembers().size(); int memberCount = groupRecord.getMembers().size();
String description = getMemberCountDescription(context, memberCount); String description = getMemberCountDescription(context, memberCount);
Optional<Attachment> thumbnail = Optional.empty(); Optional<Attachment> thumbnail = Optional.empty();
@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.RecipientTable; import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.ThreadTable; import org.thoughtcrime.securesms.database.ThreadTable;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
@@ -121,7 +122,7 @@ class CameraContactsRepository {
List<Recipient> recipients = new ArrayList<>(); List<Recipient> recipients = new ArrayList<>();
try (GroupTable.Reader reader = groupDatabase.queryGroupsByTitle(query, false, true, true)) { try (GroupTable.Reader reader = groupDatabase.queryGroupsByTitle(query, false, true, true)) {
GroupTable.GroupRecord groupRecord; GroupRecord groupRecord;
while ((groupRecord = reader.getNext()) != null) { while ((groupRecord = reader.getNext()) != null) {
RecipientId recipientId = recipientTable.getOrInsertFromGroupId(groupRecord.getId()); RecipientId recipientId = recipientTable.getOrInsertFromGroupId(groupRecord.getId());
recipients.add(Recipient.resolved(recipientId)); recipients.add(Recipient.resolved(recipientId));
@@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.database.RecipientTable; import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.ThreadTable; import org.thoughtcrime.securesms.database.ThreadTable;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupChangeException; import org.thoughtcrime.securesms.groups.GroupChangeException;
import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.GroupManager;
@@ -58,8 +59,8 @@ final class MessageRequestRepository {
void getGroupInfo(@NonNull RecipientId recipientId, @NonNull Consumer<GroupInfo> onGroupInfoLoaded) { void getGroupInfo(@NonNull RecipientId recipientId, @NonNull Consumer<GroupInfo> onGroupInfoLoaded) {
executor.execute(() -> { executor.execute(() -> {
GroupTable groupDatabase = SignalDatabase.groups(); GroupTable groupDatabase = SignalDatabase.groups();
Optional<GroupTable.GroupRecord> groupRecord = groupDatabase.getGroup(recipientId); Optional<GroupRecord> groupRecord = groupDatabase.getGroup(recipientId);
onGroupInfoLoaded.accept(groupRecord.map(record -> { onGroupInfoLoaded.accept(groupRecord.map(record -> {
if (record.isV2Group()) { if (record.isV2Group()) {
DecryptedGroup decryptedGroup = record.requireV2GroupProperties().getDecryptedGroup(); DecryptedGroup decryptedGroup = record.requireV2GroupProperties().getDecryptedGroup();
@@ -12,7 +12,7 @@ import org.signal.libsignal.protocol.InvalidRegistrationIdException;
import org.signal.libsignal.protocol.NoSessionException; import org.signal.libsignal.protocol.NoSessionException;
import org.thoughtcrime.securesms.crypto.SenderKeyUtil; import org.thoughtcrime.securesms.crypto.SenderKeyUtil;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.GroupTable.GroupRecord; import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.MessageSendLogTables; import org.thoughtcrime.securesms.database.MessageSendLogTables;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.DistributionListId; import org.thoughtcrime.securesms.database.model.DistributionListId;
@@ -35,7 +35,7 @@ import org.thoughtcrime.securesms.crypto.SecurityEvent;
import org.thoughtcrime.securesms.database.AttachmentTable; import org.thoughtcrime.securesms.database.AttachmentTable;
import org.thoughtcrime.securesms.database.CallTable; import org.thoughtcrime.securesms.database.CallTable;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.GroupTable.GroupRecord; import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.GroupReceiptTable; import org.thoughtcrime.securesms.database.GroupReceiptTable;
import org.thoughtcrime.securesms.database.GroupReceiptTable.GroupReceiptInfo; import org.thoughtcrime.securesms.database.GroupReceiptTable.GroupReceiptInfo;
import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.MessageTable;
@@ -18,9 +18,9 @@ import androidx.core.graphics.drawable.IconCompat
import org.signal.core.util.PendingIntentFlags.mutable import org.signal.core.util.PendingIntentFlags.mutable
import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.conversation.ConversationIntents import org.thoughtcrime.securesms.conversation.ConversationIntents
import org.thoughtcrime.securesms.database.GroupTable
import org.thoughtcrime.securesms.database.RecipientTable import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.GroupRecord
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.NotificationChannels import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.ReplyMethod import org.thoughtcrime.securesms.notifications.ReplyMethod
@@ -117,7 +117,7 @@ sealed class NotificationBuilder(protected val context: Context) {
fun addReplyActions(conversation: NotificationConversation) { fun addReplyActions(conversation: NotificationConversation) {
if (privacy.isDisplayMessage && isNotLocked && !conversation.recipient.isPushV1Group && RecipientUtil.isMessageRequestAccepted(context, conversation.recipient)) { if (privacy.isDisplayMessage && isNotLocked && !conversation.recipient.isPushV1Group && RecipientUtil.isMessageRequestAccepted(context, conversation.recipient)) {
if (conversation.recipient.isPushV2Group) { if (conversation.recipient.isPushV2Group) {
val group: Optional<GroupTable.GroupRecord> = SignalDatabase.groups.getGroup(conversation.recipient.requireGroupId()) val group: Optional<GroupRecord> = SignalDatabase.groups.getGroup(conversation.recipient.requireGroupId())
if (group.isPresent && group.get().isAnnouncementGroup && !group.get().isAdmin(Recipient.self())) { if (group.isPresent && group.get().isAnnouncementGroup && !group.get().isAdmin(Recipient.self())) {
return return
} }
@@ -10,8 +10,8 @@ import androidx.core.util.Consumer;
import org.signal.core.util.StreamUtil; import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor; import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.groups.GroupChangeException; import org.thoughtcrime.securesms.groups.GroupChangeException;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.GroupManager;
@@ -92,7 +92,7 @@ class EditGroupProfileRepository implements EditProfileRepository {
return SignalDatabase.groups() return SignalDatabase.groups()
.getGroup(recipientId) .getGroup(recipientId)
.map(GroupTable.GroupRecord::getDescription) .map(GroupRecord::getDescription)
.orElse(""); .orElse("");
}, descriptionConsumer::accept); }, descriptionConsumer::accept);
} }
@@ -9,6 +9,7 @@ import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails; import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
@@ -105,7 +106,7 @@ public final class ReviewUtil {
return Stream.of(SignalDatabase.groups() return Stream.of(SignalDatabase.groups()
.getPushGroupsContainingMember(recipientId)) .getPushGroupsContainingMember(recipientId))
.filter(g -> g.getMembers().contains(Recipient.self().getId())) .filter(g -> g.getMembers().contains(Recipient.self().getId()))
.map(GroupTable.GroupRecord::getRecipientId) .map(GroupRecord::getRecipientId)
.toList() .toList()
.size(); .size();
} }
@@ -15,7 +15,7 @@ import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.DistributionListTables; import org.thoughtcrime.securesms.database.DistributionListTables;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.GroupTable.GroupRecord; import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.RecipientTable; import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.DistributionListRecord; import org.thoughtcrime.securesms.database.model.DistributionListRecord;
@@ -21,6 +21,7 @@ import org.whispersystems.signalservice.api.util.UuidUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState; import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.ThreadTable; import org.thoughtcrime.securesms.database.ThreadTable;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupChangeBusyException; import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
import org.thoughtcrime.securesms.groups.GroupChangeException; import org.thoughtcrime.securesms.groups.GroupChangeException;
@@ -301,7 +302,7 @@ public class RecipientUtil {
GroupTable groupDatabase = SignalDatabase.groups(); GroupTable groupDatabase = SignalDatabase.groups();
return groupDatabase.getPushGroupsContainingMember(recipient.getId()) return groupDatabase.getPushGroupsContainingMember(recipient.getId())
.stream() .stream()
.anyMatch(GroupTable.GroupRecord::isV2Group); .anyMatch(GroupRecord::isV2Group);
} }
} }
@@ -11,6 +11,7 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery; import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.model.IdentityRecord; import org.thoughtcrime.securesms.database.model.IdentityRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupChangeException; import org.thoughtcrime.securesms.groups.GroupChangeException;
@@ -106,11 +107,11 @@ final class RecipientDialogRepository {
void getGroupMembership(@NonNull Consumer<List<RecipientId>> onComplete) { void getGroupMembership(@NonNull Consumer<List<RecipientId>> onComplete) {
SimpleTask.run(SignalExecutors.UNBOUNDED, SimpleTask.run(SignalExecutors.UNBOUNDED,
() -> { () -> {
GroupTable groupDatabase = SignalDatabase.groups(); GroupTable groupDatabase = SignalDatabase.groups();
List<GroupTable.GroupRecord> groupRecords = groupDatabase.getPushGroupsContainingMember(recipientId); List<GroupRecord> groupRecords = groupDatabase.getPushGroupsContainingMember(recipientId);
ArrayList<RecipientId> groupRecipients = new ArrayList<>(groupRecords.size()); ArrayList<RecipientId> groupRecipients = new ArrayList<>(groupRecords.size());
for (GroupTable.GroupRecord groupRecord : groupRecords) { for (GroupRecord groupRecord : groupRecords) {
groupRecipients.add(groupRecord.getRecipientId()); groupRecipients.add(groupRecord.getRecipientId());
} }
@@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SearchTable; import org.thoughtcrime.securesms.database.SearchTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.ThreadTable; import org.thoughtcrime.securesms.database.ThreadTable;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.model.Mention; import org.thoughtcrime.securesms.database.model.Mention;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.database.model.ThreadRecord;
@@ -174,7 +175,7 @@ public class SearchRepository {
Set<RecipientId> groupsByTitleIds = new LinkedHashSet<>(); Set<RecipientId> groupsByTitleIds = new LinkedHashSet<>();
GroupTable.GroupRecord record; GroupRecord record;
try (GroupTable.Reader reader = SignalDatabase.groups().queryGroupsByTitle(query, true, false, false)) { try (GroupTable.Reader reader = SignalDatabase.groups().queryGroupsByTitle(query, true, false, false)) {
while ((record = reader.getNext()) != null) { while ((record = reader.getNext()) != null) {
groupsByTitleIds.add(record.getRecipientId()); groupsByTitleIds.add(record.getRecipientId());
@@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.CallTable; import org.thoughtcrime.securesms.database.CallTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.events.GroupCallPeekEvent; import org.thoughtcrime.securesms.events.GroupCallPeekEvent;
import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.events.WebRtcViewModel;
@@ -748,9 +749,9 @@ private void processStateless(@NonNull Function1<WebRtcEphemeralState, WebRtcEph
@Override @Override
public void onGroupCallRingUpdate(@NonNull byte[] groupIdBytes, long ringId, @NonNull UUID sender, @NonNull CallManager.RingUpdate ringUpdate) { public void onGroupCallRingUpdate(@NonNull byte[] groupIdBytes, long ringId, @NonNull UUID sender, @NonNull CallManager.RingUpdate ringUpdate) {
try { try {
GroupId.V2 groupId = GroupId.v2(new GroupIdentifier(groupIdBytes)); GroupId.V2 groupId = GroupId.v2(new GroupIdentifier(groupIdBytes));
GroupTable.GroupRecord group = SignalDatabase.groups().getGroup(groupId).orElse(null); GroupRecord group = SignalDatabase.groups().getGroup(groupId).orElse(null);
Recipient senderRecipient = Recipient.externalPush(ServiceId.from(sender)); Recipient senderRecipient = Recipient.externalPush(ServiceId.from(sender));
if (group != null && if (group != null &&
group.isActive() && group.isActive() &&
@@ -9,6 +9,7 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.RecipientTable; import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.groups.BadGroupIdException; import org.thoughtcrime.securesms.groups.BadGroupIdException;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
@@ -46,8 +47,8 @@ public final class GroupV1RecordProcessor extends DefaultStorageRecordProcessor<
@Override @Override
boolean isInvalid(@NonNull SignalGroupV1Record remote) { boolean isInvalid(@NonNull SignalGroupV1Record remote) {
try { try {
GroupId.V1 id = GroupId.v1(remote.getGroupId()); GroupId.V1 id = GroupId.v1(remote.getGroupId());
Optional<GroupTable.GroupRecord> v2Record = groupDatabase.getGroup(id.deriveV2MigrationGroupId()); Optional<GroupRecord> v2Record = groupDatabase.getGroup(id.deriveV2MigrationGroupId());
if (v2Record.isPresent()) { if (v2Record.isPresent()) {
Log.w(TAG, "We already have an upgraded V2 group for this V1 group -- marking as invalid."); Log.w(TAG, "We already have an upgraded V2 group for this V1 group -- marking as invalid.");
@@ -29,8 +29,8 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.WebRtcCallActivity; import org.thoughtcrime.securesms.WebRtcCallActivity;
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery; import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
import org.thoughtcrime.securesms.conversation.ConversationIntents; import org.thoughtcrime.securesms.conversation.ConversationIntents;
import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.ui.invitesandrequests.joining.GroupJoinBottomSheetDialogFragment; import org.thoughtcrime.securesms.groups.ui.invitesandrequests.joining.GroupJoinBottomSheetDialogFragment;
@@ -237,7 +237,7 @@ public class CommunicationActions {
GroupId.V2 groupId = GroupId.v2(groupInviteLinkUrl.getGroupMasterKey()); GroupId.V2 groupId = GroupId.v2(groupInviteLinkUrl.getGroupMasterKey());
SimpleTask.run(SignalExecutors.BOUNDED, () -> { SimpleTask.run(SignalExecutors.BOUNDED, () -> {
GroupTable.GroupRecord group = SignalDatabase.groups().getGroup(groupId).orElse(null); GroupRecord group = SignalDatabase.groups().getGroup(groupId).orElse(null);
return group != null && group.isActive() ? Recipient.resolved(group.getRecipientId()) return group != null && group.isActive() ? Recipient.resolved(group.getRecipientId())
: null; : null;
@@ -14,6 +14,7 @@ import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.mms.MessageGroupContext; import org.thoughtcrime.securesms.mms.MessageGroupContext;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
@@ -94,7 +95,7 @@ public final class GroupUtil {
{ {
if (groupId.isV2()) { if (groupId.isV2()) {
GroupTable groupDatabase = SignalDatabase.groups(); GroupTable groupDatabase = SignalDatabase.groups();
GroupTable.GroupRecord groupRecord = groupDatabase.requireGroup(groupId); GroupRecord groupRecord = groupDatabase.requireGroup(groupId);
GroupTable.V2GroupProperties v2GroupProperties = groupRecord.requireV2GroupProperties(); GroupTable.V2GroupProperties v2GroupProperties = groupRecord.requireV2GroupProperties();
SignalServiceGroupV2 group = SignalServiceGroupV2.newBuilder(v2GroupProperties.getGroupMasterKey()) SignalServiceGroupV2 group = SignalServiceGroupV2.newBuilder(v2GroupProperties.getGroupMasterKey())
.withRevision(v2GroupProperties.getGroupRevision()) .withRevision(v2GroupProperties.getGroupRevision())
@@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.database.IdentityTable;
import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.database.MessageTable.InsertResult; import org.thoughtcrime.securesms.database.MessageTable.InsertResult;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.model.IdentityRecord; import org.thoughtcrime.securesms.database.model.IdentityRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
@@ -67,7 +68,7 @@ public final class IdentityUtil {
try (GroupTable.Reader reader = groupDatabase.getGroups()) { try (GroupTable.Reader reader = groupDatabase.getGroups()) {
GroupTable.GroupRecord groupRecord; GroupRecord groupRecord;
while ((groupRecord = reader.getNext()) != null) { while ((groupRecord = reader.getNext()) != null) {
if (groupRecord.getMembers().contains(recipient.getId()) && groupRecord.isActive() && !groupRecord.isMms()) { if (groupRecord.getMembers().contains(recipient.getId()) && groupRecord.isActive() && !groupRecord.isMms()) {
@@ -138,7 +139,7 @@ public final class IdentityUtil {
GroupTable groupDatabase = SignalDatabase.groups(); GroupTable groupDatabase = SignalDatabase.groups();
try (GroupTable.Reader reader = groupDatabase.getGroups()) { try (GroupTable.Reader reader = groupDatabase.getGroups()) {
GroupTable.GroupRecord groupRecord; GroupRecord groupRecord;
while ((groupRecord = reader.getNext()) != null) { while ((groupRecord = reader.getNext()) != null) {
if (groupRecord.getMembers().contains(recipientId) && groupRecord.isActive()) { if (groupRecord.getMembers().contains(recipientId) && groupRecord.isActive()) {
@@ -13,6 +13,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember
import org.signal.storageservice.protos.groups.local.DecryptedString import org.signal.storageservice.protos.groups.local.DecryptedString
import org.signal.storageservice.protos.groups.local.DecryptedTimer import org.signal.storageservice.protos.groups.local.DecryptedTimer
import org.signal.storageservice.protos.groups.local.EnabledState import org.signal.storageservice.protos.groups.local.EnabledState
import org.thoughtcrime.securesms.database.model.GroupRecord
import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupHistoryEntry import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupHistoryEntry
@@ -99,7 +100,7 @@ class GroupChangeData(private val revision: Int, private val groupOperations: Gr
class GroupStateTestData(private val masterKey: GroupMasterKey, private val groupOperations: GroupsV2Operations.GroupOperations? = null) { class GroupStateTestData(private val masterKey: GroupMasterKey, private val groupOperations: GroupsV2Operations.GroupOperations? = null) {
var localState: DecryptedGroup? = null var localState: DecryptedGroup? = null
var groupRecord: Optional<GroupTable.GroupRecord> = Optional.empty() var groupRecord: Optional<GroupRecord> = Optional.empty()
var serverState: DecryptedGroup? = null var serverState: DecryptedGroup? = null
var changeSet: ChangeSet? = null var changeSet: ChangeSet? = null
var groupChange: GroupChange? = null var groupChange: GroupChange? = null
@@ -172,9 +173,9 @@ fun groupRecord(
avatarDigest: ByteArray = ByteArray(0), avatarDigest: ByteArray = ByteArray(0),
mms: Boolean = false, mms: Boolean = false,
distributionId: DistributionId? = null distributionId: DistributionId? = null
): Optional<GroupTable.GroupRecord> { ): Optional<GroupRecord> {
return Optional.of( return Optional.of(
GroupTable.GroupRecord( GroupRecord(
id, id,
recipientId, recipientId,
decryptedGroup.title, decryptedGroup.title,
@@ -4,6 +4,10 @@ package org.thoughtcrime.securesms.groups
import android.app.Application import android.app.Application
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers import org.hamcrest.Matchers
import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.`is`
@@ -11,9 +15,6 @@ import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.robolectric.RobolectricTestRunner import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
import org.signal.core.util.Hex import org.signal.core.util.Hex
@@ -71,8 +72,6 @@ class GroupManagerV2Test_edit {
private lateinit var sendGroupUpdateHelper: GroupManagerV2.SendGroupUpdateHelper private lateinit var sendGroupUpdateHelper: GroupManagerV2.SendGroupUpdateHelper
private lateinit var groupOperations: GroupsV2Operations.GroupOperations private lateinit var groupOperations: GroupsV2Operations.GroupOperations
private lateinit var patchedDecryptedGroup: ArgumentCaptor<DecryptedGroup>
private lateinit var manager: GroupManagerV2 private lateinit var manager: GroupManagerV2
@get:Rule @get:Rule
@@ -87,17 +86,15 @@ class GroupManagerV2Test_edit {
val clientZkOperations = ClientZkOperations(server.getServerPublicParams()) val clientZkOperations = ClientZkOperations(server.getServerPublicParams())
groupTable = mock(GroupTable::class.java) groupTable = mockk()
groupsV2API = mock(GroupsV2Api::class.java) groupsV2API = mockk()
groupsV2Operations = GroupsV2Operations(clientZkOperations, 1000) groupsV2Operations = GroupsV2Operations(clientZkOperations, 1000)
groupsV2Authorization = mock(GroupsV2Authorization::class.java) groupsV2Authorization = mockk(relaxed = true)
groupsV2StateProcessor = mock(GroupsV2StateProcessor::class.java) groupsV2StateProcessor = mockk()
groupCandidateHelper = mock(GroupCandidateHelper::class.java) groupCandidateHelper = mockk()
sendGroupUpdateHelper = mock(GroupManagerV2.SendGroupUpdateHelper::class.java) sendGroupUpdateHelper = mockk()
groupOperations = groupsV2Operations.forGroup(groupSecretParams) groupOperations = groupsV2Operations.forGroup(groupSecretParams)
patchedDecryptedGroup = ArgumentCaptor.forClass(DecryptedGroup::class.java)
manager = GroupManagerV2( manager = GroupManagerV2(
ApplicationProvider.getApplicationContext(), ApplicationProvider.getApplicationContext(),
groupTable, groupTable,
@@ -115,12 +112,11 @@ class GroupManagerV2Test_edit {
val data = GroupStateTestData(masterKey, groupOperations) val data = GroupStateTestData(masterKey, groupOperations)
data.init() data.init()
Mockito.doReturn(data.groupRecord).`when`(groupTable).getGroup(groupId) every { groupTable.getGroup(groupId) } returns data.groupRecord
Mockito.doReturn(data.groupRecord.get()).`when`(groupTable).requireGroup(groupId) every { groupTable.requireGroup(groupId) } returns data.groupRecord.get()
every { groupTable.update(any<GroupId.V2>(), any()) } returns Unit
Mockito.doReturn(GroupManagerV2.RecipientAndThread(Recipient.UNKNOWN, 1)).`when`(sendGroupUpdateHelper).sendGroupUpdate(Mockito.eq(masterKey), Mockito.any(), Mockito.any(), Mockito.anyBoolean()) every { sendGroupUpdateHelper.sendGroupUpdate(masterKey, any(), any(), any()) } returns GroupManagerV2.RecipientAndThread(Recipient.UNKNOWN, 1)
every { groupsV2API.patchGroup(any(), any(), any()) } returns data.groupChange!!
Mockito.doReturn(data.groupChange!!).`when`(groupsV2API).patchGroup(Mockito.any(), Mockito.any(), Mockito.any())
} }
private fun editGroup(perform: GroupManagerV2.GroupEditor.() -> Unit) { private fun editGroup(perform: GroupManagerV2.GroupEditor.() -> Unit) {
@@ -128,8 +124,9 @@ class GroupManagerV2Test_edit {
} }
private fun then(then: (DecryptedGroup) -> Unit) { private fun then(then: (DecryptedGroup) -> Unit) {
Mockito.verify(groupTable).update(Mockito.eq(groupId), patchedDecryptedGroup.capture()) val decryptedGroupArg = slot<DecryptedGroup>()
then(patchedDecryptedGroup.value) verify { groupTable.update(groupId, capture(decryptedGroupArg)) }
then(decryptedGroupArg.captured)
} }
@Test @Test
@@ -2,6 +2,10 @@ package org.thoughtcrime.securesms.groups.v2.processing
import android.app.Application import android.app.Application
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.verify
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.both import org.hamcrest.Matchers.both
import org.hamcrest.Matchers.hasItem import org.hamcrest.Matchers.hasItem
@@ -13,18 +17,6 @@ import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.any
import org.mockito.Mockito.doCallRealMethod
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.isA
import org.mockito.Mockito.mock
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doNothing
import org.robolectric.RobolectricTestRunner import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
import org.signal.core.util.Hex.fromStringCondensed import org.signal.core.util.Hex.fromStringCondensed
@@ -48,6 +40,7 @@ import org.thoughtcrime.securesms.database.setNewTitle
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupsV2Authorization import org.thoughtcrime.securesms.groups.GroupsV2Authorization
import org.thoughtcrime.securesms.jobmanager.JobManager
import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger
import org.thoughtcrime.securesms.testutil.SystemOutLogger import org.thoughtcrime.securesms.testutil.SystemOutLogger
@@ -77,6 +70,7 @@ class GroupsV2StateProcessorTest {
private lateinit var groupsV2API: GroupsV2Api private lateinit var groupsV2API: GroupsV2Api
private lateinit var groupsV2Authorization: GroupsV2Authorization private lateinit var groupsV2Authorization: GroupsV2Authorization
private lateinit var profileAndMessageHelper: GroupsV2StateProcessor.ProfileAndMessageHelper private lateinit var profileAndMessageHelper: GroupsV2StateProcessor.ProfileAndMessageHelper
private lateinit var jobManager: JobManager
private lateinit var processor: GroupsV2StateProcessor.StateProcessorForGroup private lateinit var processor: GroupsV2StateProcessor.StateProcessorForGroup
@@ -88,25 +82,29 @@ class GroupsV2StateProcessorTest {
Log.initialize(SystemOutLogger()) Log.initialize(SystemOutLogger())
SignalProtocolLoggerProvider.setProvider(CustomSignalProtocolLogger()) SignalProtocolLoggerProvider.setProvider(CustomSignalProtocolLogger())
groupTable = mock(GroupTable::class.java) groupTable = mockk(relaxed = true)
recipientTable = mock(RecipientTable::class.java) recipientTable = mockk()
groupsV2API = mock(GroupsV2Api::class.java) groupsV2API = mockk()
groupsV2Authorization = mock(GroupsV2Authorization::class.java) groupsV2Authorization = mockk(relaxed = true)
profileAndMessageHelper = mock(GroupsV2StateProcessor.ProfileAndMessageHelper::class.java) profileAndMessageHelper = mockk(relaxed = true)
jobManager = mockk(relaxed = true)
mockkStatic(ApplicationDependencies::class)
every { ApplicationDependencies.getJobManager() } returns jobManager
processor = GroupsV2StateProcessor.StateProcessorForGroup(serviceIds, ApplicationProvider.getApplicationContext(), groupTable, groupsV2API, groupsV2Authorization, masterKey, profileAndMessageHelper) processor = GroupsV2StateProcessor.StateProcessorForGroup(serviceIds, ApplicationProvider.getApplicationContext(), groupTable, groupsV2API, groupsV2Authorization, masterKey, profileAndMessageHelper)
} }
@After @After
fun tearDown() { fun tearDown() {
reset(ApplicationDependencies.getJobManager()) // reset(ApplicationDependencies.getJobManager())
} }
private fun given(init: GroupStateTestData.() -> Unit) { private fun given(init: GroupStateTestData.() -> Unit) {
val data = givenData(init) val data = givenData(init)
doReturn(data.groupRecord).`when`(groupTable).getGroup(any(GroupId.V2::class.java)) every { groupTable.getGroup(any<GroupId.V2>()) } returns data.groupRecord
doReturn(!data.groupRecord.isPresent).`when`(groupTable).isUnknownGroup(any()) every { groupTable.isUnknownGroup(any()) } returns !data.groupRecord.isPresent
data.serverState?.let { serverState -> data.serverState?.let { serverState ->
val testPartial = object : PartialDecryptedGroup(null, serverState, null, null) { val testPartial = object : PartialDecryptedGroup(null, serverState, null, null) {
@@ -114,12 +112,13 @@ class GroupsV2StateProcessorTest {
return serverState return serverState
} }
} }
doReturn(testPartial).`when`(groupsV2API).getPartialDecryptedGroup(any(), any())
doReturn(serverState).`when`(groupsV2API).getGroup(any(), any()) every { groupsV2API.getPartialDecryptedGroup(any(), any()) } returns testPartial
every { groupsV2API.getGroup(any(), any()) } returns serverState
} }
data.changeSet?.let { changeSet -> data.changeSet?.let { changeSet ->
doReturn(changeSet.toApiResponse()).`when`(groupsV2API).getGroupHistoryPage(any(), eq(data.requestedRevision), any(), eq(data.includeFirst)) every { groupsV2API.getGroupHistoryPage(any(), data.requestedRevision, any(), data.includeFirst) } returns changeSet.toApiResponse()
} }
} }
@@ -306,15 +305,14 @@ class GroupsV2StateProcessorTest {
apiCallParameters(2, true) apiCallParameters(2, true)
} }
doReturn(true).`when`(groupTable).isUnknownGroup(any()) every { groupTable.isUnknownGroup(any()) } returns true
val result = processor.updateLocalGroupToRevision(2, 0, DecryptedGroupChange.getDefaultInstance()) val result = processor.updateLocalGroupToRevision(2, 0, DecryptedGroupChange.getDefaultInstance())
assertThat("local should update to revision added", result.groupState, `is`(GroupsV2StateProcessor.GroupState.GROUP_UPDATED)) assertThat("local should update to revision added", result.groupState, `is`(GroupsV2StateProcessor.GroupState.GROUP_UPDATED))
assertThat("revision matches peer revision added", result.latestServer!!.revision, `is`(2)) assertThat("revision matches peer revision added", result.latestServer!!.revision, `is`(2))
assertThat("title matches that as it was in revision added", result.latestServer!!.title, `is`("Baking Signal for Science")) assertThat("title matches that as it was in revision added", result.latestServer!!.title, `is`("Baking Signal for Science"))
verify { jobManager.add(ofType(RequestGroupV2InfoJob::class)) }
verify(ApplicationDependencies.getJobManager()).add(isA(RequestGroupV2InfoJob::class.java))
} }
@Test @Test
@@ -406,7 +404,7 @@ class GroupsV2StateProcessorTest {
assertThat("local should update to server", result.groupState, `is`(GroupsV2StateProcessor.GroupState.GROUP_UPDATED)) assertThat("local should update to server", result.groupState, `is`(GroupsV2StateProcessor.GroupState.GROUP_UPDATED))
assertThat("revision matches revision approved at", result.latestServer!!.revision, `is`(3)) assertThat("revision matches revision approved at", result.latestServer!!.revision, `is`(3))
assertThat("title matches revision approved at", result.latestServer!!.title, `is`("Beam me up")) assertThat("title matches revision approved at", result.latestServer!!.title, `is`("Beam me up"))
verify(ApplicationDependencies.getJobManager()).add(isA(RequestGroupV2InfoJob::class.java)) verify { jobManager.add(ofType(RequestGroupV2InfoJob::class)) }
} }
@Test @Test
@@ -458,7 +456,7 @@ class GroupsV2StateProcessorTest {
} }
} }
} }
doReturn(secondApiCallChangeSet.changeSet!!.toApiResponse()).`when`(groupsV2API).getGroupHistoryPage(any(), eq(100), any(), eq(true)) every { groupsV2API.getGroupHistoryPage(any(), 100, any(), true) } returns secondApiCallChangeSet.changeSet!!.toApiResponse()
val result = processor.updateLocalGroupToRevision(GroupsV2StateProcessor.LATEST, 0, null) val result = processor.updateLocalGroupToRevision(GroupsV2StateProcessor.LATEST, 0, null)
@@ -474,10 +472,11 @@ class GroupsV2StateProcessorTest {
fun missedMemberAddResolvesWithMultipleRevisionUpdate() { fun missedMemberAddResolvesWithMultipleRevisionUpdate() {
val secondOther = member(ServiceId.from(UUID.randomUUID())) val secondOther = member(ServiceId.from(UUID.randomUUID()))
val updateMessageContextCapture = ArgumentCaptor.forClass(DecryptedGroupV2Context::class.java)
profileAndMessageHelper.masterKey = masterKey profileAndMessageHelper.masterKey = masterKey
doCallRealMethod().`when`(profileAndMessageHelper).insertUpdateMessages(anyLong(), anyOrNull(), any())
doNothing().`when`(profileAndMessageHelper).storeMessage(updateMessageContextCapture.capture(), anyLong()) val updateMessageContextArgs = mutableListOf<DecryptedGroupV2Context>()
every { profileAndMessageHelper.insertUpdateMessages(any(), any(), any()) } answers { callOriginal() }
every { profileAndMessageHelper.storeMessage(capture(updateMessageContextArgs), any()) } returns Unit
given { given {
localState( localState(
@@ -513,8 +512,7 @@ class GroupsV2StateProcessorTest {
assertThat("local should update to server", result.groupState, `is`(GroupsV2StateProcessor.GroupState.GROUP_UPDATED)) assertThat("local should update to server", result.groupState, `is`(GroupsV2StateProcessor.GroupState.GROUP_UPDATED))
assertThat("members contains second other", result.latestServer!!.membersList, hasItem(secondOther)) assertThat("members contains second other", result.latestServer!!.membersList, hasItem(secondOther))
val allUpdateMessageContexts = updateMessageContextCapture.allValues assertThat("group update messages contains new member add", updateMessageContextArgs.map { it.change.newMembersList }, hasItem(hasItem(secondOther)))
assertThat("group update messages contains new member add", allUpdateMessageContexts.map { it.change.newMembersList }, hasItem(hasItem(secondOther)))
} }
/** /**
@@ -525,10 +523,11 @@ class GroupsV2StateProcessorTest {
fun missedMemberAddResolvesWithForcedUpdate() { fun missedMemberAddResolvesWithForcedUpdate() {
val secondOther = member(ServiceId.from(UUID.randomUUID())) val secondOther = member(ServiceId.from(UUID.randomUUID()))
val updateMessageContextCapture = ArgumentCaptor.forClass(DecryptedGroupV2Context::class.java)
profileAndMessageHelper.masterKey = masterKey profileAndMessageHelper.masterKey = masterKey
doCallRealMethod().`when`(profileAndMessageHelper).insertUpdateMessages(anyLong(), anyOrNull(), any())
doNothing().`when`(profileAndMessageHelper).storeMessage(updateMessageContextCapture.capture(), anyLong()) val updateMessageContextArgs = mutableListOf<DecryptedGroupV2Context>()
every { profileAndMessageHelper.insertUpdateMessages(any(), any(), any()) } answers { callOriginal() }
every { profileAndMessageHelper.storeMessage(capture(updateMessageContextArgs), any()) } returns Unit
given { given {
localState( localState(
@@ -548,12 +547,11 @@ class GroupsV2StateProcessorTest {
assertThat("members contains second other", result.latestServer!!.membersList, hasItem(secondOther)) assertThat("members contains second other", result.latestServer!!.membersList, hasItem(secondOther))
assertThat("title should be updated", result.latestServer!!.title, `is`("Changed")) assertThat("title should be updated", result.latestServer!!.title, `is`("Changed"))
val allUpdateMessageContexts = updateMessageContextCapture.allValues assertThat("group update messages contains new member add", updateMessageContextArgs.map { it.change.newMembersList }, hasItem(hasItem(secondOther)))
assertThat("group update messages contains new member add", allUpdateMessageContexts.map { it.change.newMembersList }, hasItem(hasItem(secondOther)))
assertThat( assertThat(
"group update messages contains title change", "group update messages contains title change",
allUpdateMessageContexts.map { it.change.newTitle }, updateMessageContextArgs.map { it.change.newTitle },
hasItem(both<DecryptedString>(notNullValue()).and(hasProperty("value", `is`("Changed")))) hasItem(both<DecryptedString>(notNullValue()).and(hasProperty("value", `is`("Changed"))))
) )
} }
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.jobmanager.migrations;
import org.junit.Test; import org.junit.Test;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.JobMigration; import org.thoughtcrime.securesms.jobmanager.JobMigration;
@@ -34,7 +35,7 @@ public class SenderKeyDistributionSendJobRecipientMigrationTest {
.putBlobAsString("group_id", GROUP_ID.getDecodedId()) .putBlobAsString("group_id", GROUP_ID.getDecodedId())
.build()); .build());
GroupTable.GroupRecord mockGroup = mock(GroupTable.GroupRecord.class); GroupRecord mockGroup = mock(GroupRecord.class);
when(mockGroup.getRecipientId()).thenReturn(RecipientId.from(2)); when(mockGroup.getRecipientId()).thenReturn(RecipientId.from(2));
when(mockDatabase.getGroup(GROUP_ID)).thenReturn(Optional.of(mockGroup)); when(mockDatabase.getGroup(GROUP_ID)).thenReturn(Optional.of(mockGroup));
@@ -92,6 +92,16 @@ fun <T> Cursor.readToSingleObject(serializer: Serializer<T, Cursor>): T? {
} }
} }
fun <T> Cursor.readToSingleObject(mapper: (Cursor) -> T): T? {
return use {
if (it.moveToFirst()) {
mapper(it)
} else {
null
}
}
}
@JvmOverloads @JvmOverloads
fun Cursor.readToSingleInt(defaultValue: Int = 0): Int { fun Cursor.readToSingleInt(defaultValue: Int = 0): Int {
return use { return use {