package org.thoughtcrime.securesms.groups.ui.pendingmemberinvites; import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.WorkerThread; import androidx.core.util.Consumer; import com.annimon.stream.Stream; import com.google.protobuf.ByteString; import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.storageservice.protos.groups.local.DecryptedPendingMember; import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.groups.UuidCiphertext; import org.signal.zkgroup.util.UUIDUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.groups.GroupChangeException; import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.GroupProtoUtil; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; /** * Repository for modifying the pending members on a single group. */ final class PendingMemberRepository { private static final String TAG = Log.tag(PendingMemberRepository.class); private final Context context; private final GroupId.V2 groupId; private final Executor executor; PendingMemberRepository(@NonNull Context context, @NonNull GroupId.V2 groupId) { this.context = context.getApplicationContext(); this.executor = SignalExecutors.BOUNDED; this.groupId = groupId; } public void getInvitees(@NonNull Consumer onInviteesLoaded) { executor.execute(() -> { GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); GroupDatabase.V2GroupProperties v2GroupProperties = groupDatabase.getGroup(groupId).get().requireV2GroupProperties(); DecryptedGroup decryptedGroup = v2GroupProperties.getDecryptedGroup(); List pendingMembersList = decryptedGroup.getPendingMembersList(); List byMe = new ArrayList<>(pendingMembersList.size()); List byOthers = new ArrayList<>(pendingMembersList.size()); ByteString self = ByteString.copyFrom(UUIDUtil.serialize(Recipient.self().getUuid().get())); boolean selfIsAdmin = v2GroupProperties.isAdmin(Recipient.self()); Stream.of(pendingMembersList) .groupBy(DecryptedPendingMember::getAddedByUuid) .forEach(g -> { ByteString inviterUuid = g.getKey(); List invitedMembers = g.getValue(); if (self.equals(inviterUuid)) { for (DecryptedPendingMember pendingMember : invitedMembers) { try { Recipient invitee = GroupProtoUtil.pendingMemberToRecipient(context, pendingMember); UuidCiphertext uuidCipherText = new UuidCiphertext(pendingMember.getUuidCipherText().toByteArray()); byMe.add(new SinglePendingMemberInvitedByYou(invitee, uuidCipherText)); } catch (InvalidInputException e) { Log.w(TAG, e); } } } else { Recipient inviter = GroupProtoUtil.uuidByteStringToRecipient(context, inviterUuid); ArrayList uuidCipherTexts = new ArrayList<>(invitedMembers.size()); for (DecryptedPendingMember pendingMember : invitedMembers) { try { uuidCipherTexts.add(new UuidCiphertext(pendingMember.getUuidCipherText().toByteArray())); } catch (InvalidInputException e) { Log.w(TAG, e); } } byOthers.add(new MultiplePendingMembersInvitedByAnother(inviter, uuidCipherTexts)); } } ); onInviteesLoaded.accept(new InviteeResult(byMe, byOthers, selfIsAdmin)); }); } @WorkerThread boolean revokeInvites(@NonNull Collection uuidCipherTexts) { try { GroupManager.revokeInvites(context, groupId, uuidCipherTexts); return true; } catch (GroupChangeException | IOException e) { Log.w(TAG, e); return false; } } public static final class InviteeResult { private final List byMe; private final List byOthers; private final boolean canRevokeInvites; private InviteeResult(List byMe, List byOthers, boolean canRevokeInvites) { this.byMe = byMe; this.byOthers = byOthers; this.canRevokeInvites = canRevokeInvites; } public List getByMe() { return byMe; } public List getByOthers() { return byOthers; } public boolean isCanRevokeInvites() { return canRevokeInvites; } } public final static class SinglePendingMemberInvitedByYou { private final Recipient invitee; private final UuidCiphertext inviteeCipherText; private SinglePendingMemberInvitedByYou(@NonNull Recipient invitee, @NonNull UuidCiphertext inviteeCipherText) { this.invitee = invitee; this.inviteeCipherText = inviteeCipherText; } public Recipient getInvitee() { return invitee; } public UuidCiphertext getInviteeCipherText() { return inviteeCipherText; } } public final static class MultiplePendingMembersInvitedByAnother { private final Recipient inviter; private final Collection uuidCipherTexts; private MultiplePendingMembersInvitedByAnother(@NonNull Recipient inviter, @NonNull Collection uuidCipherTexts) { this.inviter = inviter; this.uuidCipherTexts = uuidCipherTexts; } public Recipient getInviter() { return inviter; } public Collection getUuidCipherTexts() { return uuidCipherTexts; } } }