Signal-Android/app/src/main/java/org/thoughtcrime/securesms/groups/ui/managegroup/ManageGroupRepository.java

233 lines
8.4 KiB
Java

package org.thoughtcrime.securesms.groups.ui.managegroup;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import androidx.core.util.Consumer;
import com.annimon.stream.Stream;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.thoughtcrime.securesms.ContactSelectionListFragment;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.groups.GroupAccessControl;
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.groups.MembershipNotSuitableForV2Exception;
import org.thoughtcrime.securesms.groups.ui.GroupChangeErrorCallback;
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.util.AsynchronousCallback;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
final class ManageGroupRepository {
private static final String TAG = Log.tag(ManageGroupRepository.class);
private final Context context;
private final GroupId groupId;
ManageGroupRepository(@NonNull Context context, @NonNull GroupId groupId) {
this.context = context;
this.groupId = groupId;
}
public GroupId getGroupId() {
return groupId;
}
void getGroupState(@NonNull Consumer<GroupStateResult> onGroupStateLoaded) {
SignalExecutors.BOUNDED.execute(() -> onGroupStateLoaded.accept(getGroupState()));
}
void getGroupCapacity(@NonNull Consumer<GroupCapacityResult> onGroupCapacityLoaded) {
SimpleTask.run(SignalExecutors.BOUNDED, () -> {
GroupDatabase.GroupRecord groupRecord = DatabaseFactory.getGroupDatabase(context).getGroup(groupId).get();
if (groupRecord.isV2Group()) {
DecryptedGroup decryptedGroup = groupRecord.requireV2GroupProperties().getDecryptedGroup();
List<RecipientId> pendingMembers = Stream.of(decryptedGroup.getPendingMembersList())
.map(member -> GroupProtoUtil.uuidByteStringToRecipientId(member.getUuid()))
.toList();
List<RecipientId> members = new LinkedList<>(groupRecord.getMembers());
members.addAll(pendingMembers);
return new GroupCapacityResult(members, FeatureFlags.gv2GroupCapacity());
} else {
return new GroupCapacityResult(groupRecord.getMembers(), ContactSelectionListFragment.NO_LIMIT);
}
}, onGroupCapacityLoaded::accept);
}
@WorkerThread
private GroupStateResult getGroupState() {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
long threadId = threadDatabase.getThreadIdFor(groupRecipient);
return new GroupStateResult(threadId, groupRecipient);
}
void setExpiration(int newExpirationTime, @NonNull GroupChangeErrorCallback error) {
SignalExecutors.UNBOUNDED.execute(() -> {
try {
GroupManager.updateGroupTimer(context, groupId.requirePush(), newExpirationTime);
} catch (GroupChangeException | IOException e) {
Log.w(TAG, e);
error.onError(GroupChangeFailureReason.fromException(e));
}
});
}
void applyMembershipRightsChange(@NonNull GroupAccessControl newRights, @NonNull GroupChangeErrorCallback error) {
SignalExecutors.UNBOUNDED.execute(() -> {
try {
GroupManager.applyMembershipAdditionRightsChange(context, groupId.requireV2(), newRights);
} catch (GroupChangeException | IOException e) {
Log.w(TAG, e);
error.onError(GroupChangeFailureReason.fromException(e));
}
});
}
void applyAttributesRightsChange(@NonNull GroupAccessControl newRights, @NonNull GroupChangeErrorCallback error) {
SignalExecutors.UNBOUNDED.execute(() -> {
try {
GroupManager.applyAttributesRightsChange(context, groupId.requireV2(), newRights);
} catch (GroupChangeException | IOException e) {
Log.w(TAG, e);
error.onError(GroupChangeFailureReason.fromException(e));
}
});
}
public void getRecipient(@NonNull Consumer<Recipient> recipientCallback) {
SimpleTask.run(SignalExecutors.BOUNDED,
() -> Recipient.externalGroup(context, groupId),
recipientCallback::accept);
}
void setMuteUntil(long until) {
SignalExecutors.BOUNDED.execute(() -> {
RecipientId recipientId = Recipient.externalGroup(context, groupId).getId();
DatabaseFactory.getRecipientDatabase(context).setMuted(recipientId, until);
});
}
void addMembers(@NonNull List<RecipientId> selected,
@NonNull AsynchronousCallback.WorkerThread<ManageGroupViewModel.AddMembersResult, GroupChangeFailureReason> callback)
{
SignalExecutors.UNBOUNDED.execute(() -> {
try {
GroupManager.GroupActionResult groupActionResult = GroupManager.addMembers(context, groupId.requirePush(), selected);
callback.onComplete(new ManageGroupViewModel.AddMembersResult(groupActionResult.getAddedMemberCount(), Recipient.resolvedList(groupActionResult.getInvitedMembers())));
} catch (GroupChangeException | MembershipNotSuitableForV2Exception | IOException e) {
Log.w(TAG, e);
callback.onError(GroupChangeFailureReason.fromException(e));
}
});
}
void blockAndLeaveGroup(@NonNull GroupChangeErrorCallback error, @NonNull Runnable onSuccess) {
SignalExecutors.UNBOUNDED.execute(() -> {
try {
RecipientUtil.block(context, Recipient.externalGroup(context, groupId));
onSuccess.run();
} catch (GroupChangeException | IOException e) {
Log.w(TAG, e);
error.onError(GroupChangeFailureReason.fromException(e));
}
});
}
void setMentionSetting(RecipientDatabase.MentionSetting mentionSetting) {
SignalExecutors.BOUNDED.execute(() -> {
RecipientId recipientId = Recipient.externalGroup(context, groupId).getId();
DatabaseFactory.getRecipientDatabase(context).setMentionSetting(recipientId, mentionSetting);
});
}
static final class GroupStateResult {
private final long threadId;
private final Recipient recipient;
private GroupStateResult(long threadId,
Recipient recipient)
{
this.threadId = threadId;
this.recipient = recipient;
}
long getThreadId() {
return threadId;
}
Recipient getRecipient() {
return recipient;
}
}
static final class GroupCapacityResult {
private final List<RecipientId> members;
private final int totalCapacity;
GroupCapacityResult(@NonNull List<RecipientId> members, int totalCapacity) {
this.members = members;
this.totalCapacity = totalCapacity;
}
public @NonNull List<RecipientId> getMembers() {
return members;
}
public int getTotalCapacity() {
return totalCapacity;
}
public int getSelectionLimit() {
if (totalCapacity == ContactSelectionListFragment.NO_LIMIT) {
return totalCapacity;
}
boolean containsSelf = members.indexOf(Recipient.self().getId()) != -1;
return totalCapacity - (containsSelf ? 1 : 0);
}
public int getRemainingCapacity() {
return totalCapacity - members.size();
}
public @NonNull ArrayList<RecipientId> getMembersWithoutSelf() {
ArrayList<RecipientId> recipientIds = new ArrayList<>(members.size());
RecipientId selfId = Recipient.self().getId();
for (RecipientId recipientId : members) {
if (!recipientId.equals(selfId)) {
recipientIds.add(recipientId);
}
}
return recipientIds;
}
}
}