mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 18:00:02 +01:00
Add the ability to migrate GV1 groups to GV2.
Co-authored-by: Alan Evans <alan@signal.org>
This commit is contained in:
committed by
Alan Evans
parent
2d1bf33902
commit
6bb9d27d4e
@@ -6,6 +6,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
|
||||
import org.signal.zkgroup.VerificationFailedException;
|
||||
import org.signal.zkgroup.groups.GroupMasterKey;
|
||||
@@ -81,10 +82,11 @@ public final class GroupManager {
|
||||
|
||||
@WorkerThread
|
||||
public static void migrateGroupToServer(@NonNull Context context,
|
||||
@NonNull GroupId.V1 groupIdV1)
|
||||
@NonNull GroupId.V1 groupIdV1,
|
||||
@NonNull Collection<Recipient> members)
|
||||
throws IOException, GroupChangeFailedException, MembershipNotSuitableForV2Exception, GroupAlreadyExistsException
|
||||
{
|
||||
new GroupManagerV2(context).migrateGroupOnToServer(groupIdV1);
|
||||
new GroupManagerV2(context).migrateGroupOnToServer(groupIdV1, members);
|
||||
}
|
||||
|
||||
private static Set<RecipientId> getMemberIds(Collection<Recipient> recipients) {
|
||||
@@ -186,6 +188,19 @@ public final class GroupManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to gets the exact version of the group at the time you joined.
|
||||
* <p>
|
||||
* If it fails to get the exact version, it will give the latest.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static DecryptedGroup addedGroupVersion(@NonNull Context context,
|
||||
@NonNull GroupMasterKey groupMasterKey)
|
||||
throws IOException, GroupDoesNotExistException, GroupNotAMemberException
|
||||
{
|
||||
return new GroupManagerV2(context).addedGroupVersion(groupMasterKey);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static void setMemberAdmin(@NonNull Context context,
|
||||
@NonNull GroupId.V2 groupId,
|
||||
@@ -371,6 +386,10 @@ public final class GroupManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendNoopUpdate(@NonNull Context context, @NonNull GroupMasterKey groupMasterKey, @NonNull DecryptedGroup currentState) {
|
||||
new GroupManagerV2(context).sendNoopGroupUpdate(groupMasterKey, currentState);
|
||||
}
|
||||
|
||||
public static class GroupActionResult {
|
||||
private final Recipient groupRecipient;
|
||||
private final long threadId;
|
||||
|
||||
@@ -27,8 +27,8 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
|
||||
@@ -157,7 +157,7 @@ final class GroupManagerV1 {
|
||||
|
||||
@WorkerThread
|
||||
static boolean leaveGroup(@NonNull Context context, @NonNull GroupId.V1 groupId) {
|
||||
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
|
||||
Recipient groupRecipient = Recipient.externalGroupExact(context, groupId);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
|
||||
Optional<OutgoingGroupUpdateMessage> leaveMessage = createGroupLeaveMessage(context, groupId, groupRecipient);
|
||||
|
||||
@@ -183,7 +183,7 @@ final class GroupManagerV1 {
|
||||
@WorkerThread
|
||||
static boolean silentLeaveGroup(@NonNull Context context, @NonNull GroupId.V1 groupId) {
|
||||
if (DatabaseFactory.getGroupDatabase(context).isActive(groupId)) {
|
||||
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
|
||||
Recipient groupRecipient = Recipient.externalGroupExact(context, groupId);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
|
||||
Optional<OutgoingGroupUpdateMessage> leaveMessage = createGroupLeaveMessage(context, groupId, groupRecipient);
|
||||
|
||||
@@ -208,7 +208,7 @@ final class GroupManagerV1 {
|
||||
static void updateGroupTimer(@NonNull Context context, @NonNull GroupId.V1 groupId, int expirationTime) {
|
||||
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||
Recipient recipient = Recipient.externalGroup(context, groupId);
|
||||
Recipient recipient = Recipient.externalGroupExact(context, groupId);
|
||||
long threadId = threadDatabase.getThreadIdFor(recipient);
|
||||
|
||||
recipientDatabase.setExpireMessages(recipient.getId(), expirationTime);
|
||||
@@ -228,20 +228,6 @@ final class GroupManagerV1 {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
GroupContext groupContext = GroupContext.newBuilder()
|
||||
.setId(ByteString.copyFrom(groupId.getDecodedId()))
|
||||
.setType(GroupContext.Type.QUIT)
|
||||
.build();
|
||||
|
||||
return Optional.of(new OutgoingGroupUpdateMessage(groupRecipient,
|
||||
groupContext,
|
||||
null,
|
||||
System.currentTimeMillis(),
|
||||
0,
|
||||
false,
|
||||
null,
|
||||
Collections.emptyList(),
|
||||
Collections.emptyList(),
|
||||
Collections.emptyList()));
|
||||
return Optional.of(GroupUtil.createGroupV1LeaveMessage(groupId, groupRecipient));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.signal.zkgroup.profiles.ProfileKeyCredential;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
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.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
@@ -147,7 +148,34 @@ final class GroupManagerV2 {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
void migrateGroupOnToServer(@NonNull GroupId.V1 groupIdV1)
|
||||
@NonNull DecryptedGroup addedGroupVersion(@NonNull GroupMasterKey groupMasterKey)
|
||||
throws GroupNotAMemberException, IOException, GroupDoesNotExistException
|
||||
{
|
||||
GroupsV2StateProcessor.StateProcessorForGroup stateProcessorForGroup = new GroupsV2StateProcessor(context).forGroup(groupMasterKey);
|
||||
DecryptedGroup latest = stateProcessorForGroup.getCurrentGroupStateFromServer();
|
||||
|
||||
if (latest.getRevision() == 0) {
|
||||
return latest;
|
||||
}
|
||||
|
||||
Optional<DecryptedMember> selfInFullMemberList = DecryptedGroupUtil.findMemberByUuid(latest.getMembersList(), Recipient.self().requireUuid());
|
||||
|
||||
if (!selfInFullMemberList.isPresent()) {
|
||||
return latest;
|
||||
}
|
||||
|
||||
DecryptedGroup joinedVersion = stateProcessorForGroup.getSpecificVersionFromServer(selfInFullMemberList.get().getJoinedAtRevision());
|
||||
|
||||
if (joinedVersion != null) {
|
||||
return joinedVersion;
|
||||
} else {
|
||||
Log.w(TAG, "Unable to retreive exact version joined at, using latest");
|
||||
return latest;
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
void migrateGroupOnToServer(@NonNull GroupId.V1 groupIdV1, @NonNull Collection<Recipient> members)
|
||||
throws IOException, MembershipNotSuitableForV2Exception, GroupAlreadyExistsException, GroupChangeFailedException
|
||||
{
|
||||
GroupMasterKey groupMasterKey = groupIdV1.deriveV2MigrationMasterKey();
|
||||
@@ -156,13 +184,20 @@ final class GroupManagerV2 {
|
||||
String name = groupRecord.getTitle();
|
||||
byte[] avatar = groupRecord.hasAvatar() ? AvatarHelper.getAvatarBytes(context, groupRecord.getRecipientId()) : null;
|
||||
int messageTimer = Recipient.resolved(groupRecord.getRecipientId()).getExpireMessages();
|
||||
Set<RecipientId> memberIds = Stream.of(groupRecord.getMembers())
|
||||
Set<RecipientId> memberIds = Stream.of(members)
|
||||
.map(Recipient::getId)
|
||||
.filterNot(m -> m.equals(Recipient.self().getId()))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
createGroupOnServer(groupSecretParams, name, avatar, memberIds, Member.Role.ADMINISTRATOR, messageTimer);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
void sendNoopGroupUpdate(@NonNull GroupMasterKey masterKey, @NonNull DecryptedGroup currentState) {
|
||||
sendGroupUpdate(masterKey, new GroupMutation(currentState, DecryptedGroupChange.newBuilder().build(), currentState), null);
|
||||
}
|
||||
|
||||
|
||||
final class GroupCreator extends LockOwner {
|
||||
|
||||
GroupCreator(@NonNull Closeable lock) {
|
||||
@@ -290,7 +325,7 @@ final class GroupManagerV2 {
|
||||
GroupManager.GroupActionResult groupActionResult = commitChangeWithConflictResolution(change);
|
||||
|
||||
if (avatarChanged) {
|
||||
AvatarHelper.setAvatar(context, Recipient.externalGroup(context, groupId).getId(), avatarBytes != null ? new ByteArrayInputStream(avatarBytes) : null);
|
||||
AvatarHelper.setAvatar(context, Recipient.externalGroupExact(context, groupId).getId(), avatarBytes != null ? new ByteArrayInputStream(avatarBytes) : null);
|
||||
groupDatabase.onAvatarUpdated(groupId, avatarBytes != null);
|
||||
}
|
||||
|
||||
@@ -479,7 +514,7 @@ final class GroupManagerV2 {
|
||||
|
||||
if (GroupChangeUtil.changeIsEmpty(change.build())) {
|
||||
Log.i(TAG, "Change is empty after conflict resolution");
|
||||
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
|
||||
Recipient groupRecipient = Recipient.externalGroupExact(context, groupId);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
|
||||
|
||||
return new GroupManager.GroupActionResult(groupRecipient, threadId, 0, Collections.emptyList());
|
||||
@@ -1026,7 +1061,7 @@ final class GroupManagerV2 {
|
||||
@Nullable GroupChange signedGroupChange)
|
||||
{
|
||||
GroupId.V2 groupId = GroupId.v2(masterKey);
|
||||
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
|
||||
Recipient groupRecipient = Recipient.externalGroupExact(context, groupId);
|
||||
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, groupMutation, signedGroupChange);
|
||||
OutgoingGroupUpdateMessage outgoingMessage = new OutgoingGroupUpdateMessage(groupRecipient,
|
||||
decryptedGroupV2Context,
|
||||
|
||||
@@ -9,13 +9,10 @@ import androidx.annotation.Nullable;
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.thoughtcrime.securesms.database.Database;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.MessageDatabase;
|
||||
import org.thoughtcrime.securesms.database.MessageDatabase.InsertResult;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.AvatarGroupsV1DownloadJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob;
|
||||
@@ -24,11 +21,9 @@ import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingGroupUpdateMessage;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.sms.IncomingGroupUpdateMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||
@@ -114,7 +109,7 @@ public final class GroupV1MessageProcessor {
|
||||
|
||||
if (sender.isSystemContact() || sender.isProfileSharing()) {
|
||||
Log.i(TAG, "Auto-enabling profile sharing because 'adder' is trusted. contact: " + sender.isSystemContact() + ", profileSharing: " + sender.isProfileSharing());
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.externalGroup(context, id).getId(), true);
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.externalGroupExact(context, id).getId(), true);
|
||||
}
|
||||
|
||||
return storeMessage(context, content, group, builder.build(), outgoing);
|
||||
|
||||
@@ -82,7 +82,7 @@ public final class LiveGroup {
|
||||
this.groupLink = new MutableLiveData<>(GroupLinkUrlAndStatus.NONE);
|
||||
}
|
||||
|
||||
SignalExecutors.BOUNDED.execute(() -> liveRecipient.postValue(Recipient.externalGroup(context, groupId).live()));
|
||||
SignalExecutors.BOUNDED.execute(() -> liveRecipient.postValue(Recipient.externalGroupExact(context, groupId).live()));
|
||||
}
|
||||
|
||||
protected static LiveData<List<GroupMemberEntry.FullMember>> mapToFullMembers(@NonNull LiveData<GroupDatabase.GroupRecord> groupRecord) {
|
||||
|
||||
@@ -110,7 +110,7 @@ public final class ChooseNewAdminActivity extends PassphraseRequiredActivity {
|
||||
|
||||
private void handleUpdateAndLeaveResult(@NonNull GroupChangeResult updateResult) {
|
||||
if (updateResult.isSuccess()) {
|
||||
String title = Recipient.externalGroup(this, groupId).getDisplayName(this);
|
||||
String title = Recipient.externalGroupExact(this, groupId).getDisplayName(this);
|
||||
Toast.makeText(this, getString(R.string.ChooseNewAdminActivity_you_left, title), Toast.LENGTH_LONG).show();
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
finish();
|
||||
|
||||
@@ -42,22 +42,16 @@ 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) {
|
||||
ManageGroupRepository(@NonNull Context context) {
|
||||
this.context = context;
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public GroupId getGroupId() {
|
||||
return groupId;
|
||||
void getGroupState(@NonNull GroupId groupId, @NonNull Consumer<GroupStateResult> onGroupStateLoaded) {
|
||||
SignalExecutors.BOUNDED.execute(() -> onGroupStateLoaded.accept(getGroupState(groupId)));
|
||||
}
|
||||
|
||||
void getGroupState(@NonNull Consumer<GroupStateResult> onGroupStateLoaded) {
|
||||
SignalExecutors.BOUNDED.execute(() -> onGroupStateLoaded.accept(getGroupState()));
|
||||
}
|
||||
|
||||
void getGroupCapacity(@NonNull Consumer<GroupCapacityResult> onGroupCapacityLoaded) {
|
||||
void getGroupCapacity(@NonNull GroupId groupId, @NonNull Consumer<GroupCapacityResult> onGroupCapacityLoaded) {
|
||||
SimpleTask.run(SignalExecutors.BOUNDED, () -> {
|
||||
GroupDatabase.GroupRecord groupRecord = DatabaseFactory.getGroupDatabase(context).getGroup(groupId).get();
|
||||
if (groupRecord.isV2Group()) {
|
||||
@@ -77,15 +71,15 @@ final class ManageGroupRepository {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private GroupStateResult getGroupState() {
|
||||
private GroupStateResult getGroupState(@NonNull GroupId groupId) {
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
|
||||
Recipient groupRecipient = Recipient.externalGroupExact(context, groupId);
|
||||
long threadId = threadDatabase.getThreadIdFor(groupRecipient);
|
||||
|
||||
return new GroupStateResult(threadId, groupRecipient);
|
||||
}
|
||||
|
||||
void setExpiration(int newExpirationTime, @NonNull GroupChangeErrorCallback error) {
|
||||
void setExpiration(@NonNull GroupId groupId, int newExpirationTime, @NonNull GroupChangeErrorCallback error) {
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
try {
|
||||
GroupManager.updateGroupTimer(context, groupId.requirePush(), newExpirationTime);
|
||||
@@ -96,7 +90,7 @@ final class ManageGroupRepository {
|
||||
});
|
||||
}
|
||||
|
||||
void applyMembershipRightsChange(@NonNull GroupAccessControl newRights, @NonNull GroupChangeErrorCallback error) {
|
||||
void applyMembershipRightsChange(@NonNull GroupId groupId, @NonNull GroupAccessControl newRights, @NonNull GroupChangeErrorCallback error) {
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
try {
|
||||
GroupManager.applyMembershipAdditionRightsChange(context, groupId.requireV2(), newRights);
|
||||
@@ -107,7 +101,7 @@ final class ManageGroupRepository {
|
||||
});
|
||||
}
|
||||
|
||||
void applyAttributesRightsChange(@NonNull GroupAccessControl newRights, @NonNull GroupChangeErrorCallback error) {
|
||||
void applyAttributesRightsChange(@NonNull GroupId groupId, @NonNull GroupAccessControl newRights, @NonNull GroupChangeErrorCallback error) {
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
try {
|
||||
GroupManager.applyAttributesRightsChange(context, groupId.requireV2(), newRights);
|
||||
@@ -118,20 +112,21 @@ final class ManageGroupRepository {
|
||||
});
|
||||
}
|
||||
|
||||
public void getRecipient(@NonNull Consumer<Recipient> recipientCallback) {
|
||||
public void getRecipient(@NonNull GroupId groupId, @NonNull Consumer<Recipient> recipientCallback) {
|
||||
SimpleTask.run(SignalExecutors.BOUNDED,
|
||||
() -> Recipient.externalGroup(context, groupId),
|
||||
() -> Recipient.externalGroupExact(context, groupId),
|
||||
recipientCallback::accept);
|
||||
}
|
||||
|
||||
void setMuteUntil(long until) {
|
||||
void setMuteUntil(@NonNull GroupId groupId, long until) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
RecipientId recipientId = Recipient.externalGroup(context, groupId).getId();
|
||||
RecipientId recipientId = Recipient.externalGroupExact(context, groupId).getId();
|
||||
DatabaseFactory.getRecipientDatabase(context).setMuted(recipientId, until);
|
||||
});
|
||||
}
|
||||
|
||||
void addMembers(@NonNull List<RecipientId> selected,
|
||||
void addMembers(@NonNull GroupId groupId,
|
||||
@NonNull List<RecipientId> selected,
|
||||
@NonNull AsynchronousCallback.WorkerThread<ManageGroupViewModel.AddMembersResult, GroupChangeFailureReason> callback)
|
||||
{
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
@@ -145,10 +140,10 @@ final class ManageGroupRepository {
|
||||
});
|
||||
}
|
||||
|
||||
void blockAndLeaveGroup(@NonNull GroupChangeErrorCallback error, @NonNull Runnable onSuccess) {
|
||||
void blockAndLeaveGroup(@NonNull GroupId groupId, @NonNull GroupChangeErrorCallback error, @NonNull Runnable onSuccess) {
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
try {
|
||||
RecipientUtil.block(context, Recipient.externalGroup(context, groupId));
|
||||
RecipientUtil.block(context, Recipient.externalGroupExact(context, groupId));
|
||||
onSuccess.run();
|
||||
} catch (GroupChangeException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
@@ -157,9 +152,9 @@ final class ManageGroupRepository {
|
||||
});
|
||||
}
|
||||
|
||||
void setMentionSetting(RecipientDatabase.MentionSetting mentionSetting) {
|
||||
void setMentionSetting(@NonNull GroupId groupId, RecipientDatabase.MentionSetting mentionSetting) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
RecipientId recipientId = Recipient.externalGroup(context, groupId).getId();
|
||||
RecipientId recipientId = Recipient.externalGroupExact(context, groupId).getId();
|
||||
DatabaseFactory.getRecipientDatabase(context).setMentionSetting(recipientId, mentionSetting);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -81,18 +81,18 @@ public class ManageGroupViewModel extends ViewModel {
|
||||
private final LiveData<Boolean> groupLinkOn;
|
||||
private final LiveData<GroupInfoMessage> groupInfoMessage;
|
||||
|
||||
private ManageGroupViewModel(@NonNull Context context, @NonNull ManageGroupRepository manageGroupRepository) {
|
||||
private ManageGroupViewModel(@NonNull Context context, @NonNull GroupId groupId, @NonNull ManageGroupRepository manageGroupRepository) {
|
||||
this.context = context;
|
||||
this.manageGroupRepository = manageGroupRepository;
|
||||
|
||||
manageGroupRepository.getGroupState(this::groupStateLoaded);
|
||||
manageGroupRepository.getGroupState(groupId, this::groupStateLoaded);
|
||||
|
||||
GroupId groupId = manageGroupRepository.getGroupId();
|
||||
LiveGroup liveGroup = new LiveGroup(groupId);
|
||||
|
||||
this.title = Transformations.map(liveGroup.getTitle(),
|
||||
title -> TextUtils.isEmpty(title) ? context.getString(R.string.Recipient_unknown)
|
||||
: title);
|
||||
this.groupRecipient = liveGroup.getGroupRecipient();
|
||||
this.isAdmin = liveGroup.isSelfAdmin();
|
||||
this.canCollapseMemberList = LiveDataUtil.combineLatest(memberListCollapseState,
|
||||
Transformations.map(liveGroup.getFullMembers(), m -> m.size() > MAX_UNCOLLAPSED_MEMBERS),
|
||||
@@ -102,7 +102,7 @@ public class ManageGroupViewModel extends ViewModel {
|
||||
ManageGroupViewModel::filterMemberList);
|
||||
this.pendingMemberCount = liveGroup.getPendingMemberCount();
|
||||
this.pendingAndRequestingCount = liveGroup.getPendingAndRequestingMemberCount();
|
||||
this.showLegacyIndicator = new MutableLiveData<>(groupId.isV1());
|
||||
this.showLegacyIndicator = Transformations.map(groupRecipient, recipient -> recipient.requireGroupId().isV1());
|
||||
this.memberCountSummary = LiveDataUtil.combineLatest(liveGroup.getMembershipCountDescription(context.getResources()),
|
||||
this.showLegacyIndicator,
|
||||
(description, legacy) -> legacy ? String.format("%s · %s", description, context.getString(R.string.ManageGroupActivity_legacy_group))
|
||||
@@ -113,7 +113,6 @@ public class ManageGroupViewModel extends ViewModel {
|
||||
this.disappearingMessageTimer = Transformations.map(liveGroup.getExpireMessages(), expiration -> ExpirationUtil.getExpirationDisplayValue(context, expiration));
|
||||
this.canEditGroupAttributes = liveGroup.selfCanEditGroupAttributes();
|
||||
this.canAddMembers = liveGroup.selfCanAddMembers();
|
||||
this.groupRecipient = liveGroup.getGroupRecipient();
|
||||
this.muteState = Transformations.map(this.groupRecipient,
|
||||
recipient -> new MuteState(recipient.getMuteUntil(), recipient.isMuted()));
|
||||
this.hasCustomNotifications = Transformations.map(this.groupRecipient,
|
||||
@@ -231,44 +230,47 @@ public class ManageGroupViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
void handleExpirationSelection() {
|
||||
manageGroupRepository.getRecipient(groupRecipient ->
|
||||
manageGroupRepository.getRecipient(getGroupId(),
|
||||
groupRecipient ->
|
||||
ExpirationDialog.show(context,
|
||||
groupRecipient.getExpireMessages(),
|
||||
expirationTime -> manageGroupRepository.setExpiration(expirationTime, this::showErrorToast)));
|
||||
expirationTime -> manageGroupRepository.setExpiration(getGroupId(), expirationTime, this::showErrorToast)));
|
||||
}
|
||||
|
||||
void applyMembershipRightsChange(@NonNull GroupAccessControl newRights) {
|
||||
manageGroupRepository.applyMembershipRightsChange(newRights, this::showErrorToast);
|
||||
manageGroupRepository.applyMembershipRightsChange(getGroupId(), newRights, this::showErrorToast);
|
||||
}
|
||||
|
||||
void applyAttributesRightsChange(@NonNull GroupAccessControl newRights) {
|
||||
manageGroupRepository.applyAttributesRightsChange(newRights, this::showErrorToast);
|
||||
manageGroupRepository.applyAttributesRightsChange(getGroupId(), newRights, this::showErrorToast);
|
||||
}
|
||||
|
||||
void blockAndLeave(@NonNull FragmentActivity activity) {
|
||||
manageGroupRepository.getRecipient(recipient -> BlockUnblockDialog.showBlockFor(activity,
|
||||
manageGroupRepository.getRecipient(getGroupId(),
|
||||
recipient -> BlockUnblockDialog.showBlockFor(activity,
|
||||
activity.getLifecycle(),
|
||||
recipient,
|
||||
this::onBlockAndLeaveConfirmed));
|
||||
}
|
||||
|
||||
void unblock(@NonNull FragmentActivity activity) {
|
||||
manageGroupRepository.getRecipient(recipient -> BlockUnblockDialog.showUnblockFor(activity, activity.getLifecycle(), recipient,
|
||||
manageGroupRepository.getRecipient(getGroupId(),
|
||||
recipient -> BlockUnblockDialog.showUnblockFor(activity, activity.getLifecycle(), recipient,
|
||||
() -> RecipientUtil.unblock(context, recipient)));
|
||||
}
|
||||
|
||||
void onAddMembers(@NonNull List<RecipientId> selected,
|
||||
@NonNull AsynchronousCallback.MainThread<AddMembersResult, GroupChangeFailureReason> callback)
|
||||
{
|
||||
manageGroupRepository.addMembers(selected, callback.toWorkerCallback());
|
||||
manageGroupRepository.addMembers(getGroupId(), selected, callback.toWorkerCallback());
|
||||
}
|
||||
|
||||
void setMuteUntil(long muteUntil) {
|
||||
manageGroupRepository.setMuteUntil(muteUntil);
|
||||
manageGroupRepository.setMuteUntil(getGroupId(), muteUntil);
|
||||
}
|
||||
|
||||
void clearMuteUntil() {
|
||||
manageGroupRepository.setMuteUntil(0);
|
||||
manageGroupRepository.setMuteUntil(getGroupId(), 0);
|
||||
}
|
||||
|
||||
void revealCollapsedMembers() {
|
||||
@@ -276,19 +278,24 @@ public class ManageGroupViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
void handleMentionNotificationSelection() {
|
||||
manageGroupRepository.getRecipient(r -> GroupMentionSettingDialog.show(context, r.getMentionSetting(), manageGroupRepository::setMentionSetting));
|
||||
manageGroupRepository.getRecipient(getGroupId(), r -> GroupMentionSettingDialog.show(context, r.getMentionSetting(), setting -> manageGroupRepository.setMentionSetting(getGroupId(), setting)));
|
||||
}
|
||||
|
||||
private void onBlockAndLeaveConfirmed() {
|
||||
SimpleProgressDialog.DismissibleDialog dismissibleDialog = SimpleProgressDialog.showDelayed(context);
|
||||
|
||||
manageGroupRepository.blockAndLeaveGroup(e -> {
|
||||
manageGroupRepository.blockAndLeaveGroup(getGroupId(),
|
||||
e -> {
|
||||
dismissibleDialog.dismiss();
|
||||
showErrorToast(e);
|
||||
},
|
||||
dismissibleDialog::dismiss);
|
||||
}
|
||||
|
||||
private @NonNull GroupId getGroupId() {
|
||||
return groupRecipient.getValue().requireGroupId();
|
||||
}
|
||||
|
||||
private static @NonNull List<GroupMemberEntry.FullMember> filterMemberList(@NonNull List<GroupMemberEntry.FullMember> members,
|
||||
@NonNull CollapseState collapseState)
|
||||
{
|
||||
@@ -305,13 +312,13 @@ public class ManageGroupViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
public void onAddMembersClick(@NonNull Fragment fragment, int resultCode) {
|
||||
manageGroupRepository.getGroupCapacity(capacity -> {
|
||||
manageGroupRepository.getGroupCapacity(getGroupId(), capacity -> {
|
||||
int remainingCapacity = capacity.getRemainingCapacity();
|
||||
if (remainingCapacity <= 0) {
|
||||
GroupLimitDialog.showHardLimitMessage(fragment.requireContext());
|
||||
} else {
|
||||
Intent intent = new Intent(fragment.requireActivity(), AddMembersActivity.class);
|
||||
intent.putExtra(AddMembersActivity.GROUP_ID, manageGroupRepository.getGroupId().toString());
|
||||
intent.putExtra(AddMembersActivity.GROUP_ID, getGroupId().toString());
|
||||
intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, ContactsCursorLoader.DisplayMode.FLAG_PUSH);
|
||||
intent.putExtra(ContactSelectionListFragment.SELECTION_LIMITS, new SelectionLimits(capacity.getSelectionWarning(), capacity.getSelectionLimit()));
|
||||
intent.putParcelableArrayListExtra(ContactSelectionListFragment.CURRENT_SELECTION, capacity.getMembersWithoutSelf());
|
||||
@@ -410,7 +417,7 @@ public class ManageGroupViewModel extends ViewModel {
|
||||
@Override
|
||||
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
//noinspection unchecked
|
||||
return (T) new ManageGroupViewModel(context, new ManageGroupRepository(context.getApplicationContext(), groupId));
|
||||
return (T) new ManageGroupViewModel(context, groupId, new ManageGroupRepository(context.getApplicationContext()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ public final class GroupsV2StateProcessor {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public DecryptedGroup getCurrentGroupStateFromServer()
|
||||
public @NonNull DecryptedGroup getCurrentGroupStateFromServer()
|
||||
throws IOException, GroupNotAMemberException, GroupDoesNotExistException
|
||||
{
|
||||
try {
|
||||
@@ -259,13 +259,31 @@ public final class GroupsV2StateProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public @Nullable DecryptedGroup getSpecificVersionFromServer(int revision)
|
||||
throws IOException, GroupNotAMemberException, GroupDoesNotExistException
|
||||
{
|
||||
try {
|
||||
return groupsV2Api.getGroupHistory(groupSecretParams, revision, groupsV2Authorization.getAuthorizationForToday(Recipient.self().requireUuid(), groupSecretParams))
|
||||
.get(0)
|
||||
.getGroup()
|
||||
.orNull();
|
||||
} catch (GroupNotFoundException e) {
|
||||
throw new GroupDoesNotExistException(e);
|
||||
} catch (NotInGroupException e) {
|
||||
throw new GroupNotAMemberException(e);
|
||||
} catch (VerificationFailedException | InvalidGroupStateException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void insertGroupLeave() {
|
||||
if (!groupDatabase.isActive(groupId)) {
|
||||
Log.w(TAG, "Group has already been left.");
|
||||
return;
|
||||
}
|
||||
|
||||
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
|
||||
Recipient groupRecipient = Recipient.externalGroupExact(context, groupId);
|
||||
UUID selfUuid = Recipient.self().getUuid().get();
|
||||
DecryptedGroup decryptedGroup = groupDatabase.requireGroup(groupId)
|
||||
.requireV2GroupProperties()
|
||||
@@ -368,7 +386,7 @@ public final class GroupsV2StateProcessor {
|
||||
if (addedBy.isSystemContact() || addedBy.isProfileSharing()) {
|
||||
Log.i(TAG, "Group 'adder' is trusted. contact: " + addedBy.isSystemContact() + ", profileSharing: " + addedBy.isProfileSharing());
|
||||
Log.i(TAG, "Added to a group and auto-enabling profile sharing");
|
||||
recipientDatabase.setProfileSharing(Recipient.externalGroup(context, groupId).getId(), true);
|
||||
recipientDatabase.setProfileSharing(Recipient.externalGroupExact(context, groupId).getId(), true);
|
||||
} else {
|
||||
Log.i(TAG, "Added to a group, but not enabling profile sharing, as 'adder' is not trusted");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user