Improve handling of membership changes during a GV1->GV2 migration.

This commit is contained in:
Greyson Parrelli
2020-11-24 10:54:41 -05:00
committed by Alex Hart
parent d4748efd42
commit 3804a89619
16 changed files with 241 additions and 75 deletions

View File

@@ -0,0 +1,60 @@
package org.thoughtcrime.securesms.groups;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.StringUtil;
import org.thoughtcrime.securesms.util.Util;
import java.util.Collections;
import java.util.List;
/**
* Describes a change in membership that results from a GV1->GV2 migration.
*/
public final class GroupMigrationMembershipChange {
private final List<RecipientId> pending;
private final List<RecipientId> dropped;
public GroupMigrationMembershipChange(@NonNull List<RecipientId> pending, @NonNull List<RecipientId> dropped) {
this.pending = pending;
this.dropped = dropped;
}
public static GroupMigrationMembershipChange empty() {
return new GroupMigrationMembershipChange(Collections.emptyList(), Collections.emptyList());
}
public static @NonNull GroupMigrationMembershipChange deserialize(@Nullable String serialized) {
if (Util.isEmpty(serialized)) {
return empty();
} else {
String[] parts = serialized.split("\\|");
if (parts.length == 1) {
return new GroupMigrationMembershipChange(RecipientId.fromSerializedList(parts[0]), Collections.emptyList());
} else if (parts.length == 2) {
return new GroupMigrationMembershipChange(RecipientId.fromSerializedList(parts[0]), RecipientId.fromSerializedList(parts[1]));
} else {
return GroupMigrationMembershipChange.empty();
}
}
}
public @NonNull List<RecipientId> getPending() {
return pending;
}
public @NonNull List<RecipientId> getDropped() {
return dropped;
}
public @NonNull String serialize() {
return RecipientId.toSerializedList(pending) + "|" + RecipientId.toSerializedList(dropped);
}
public boolean isEmpty() {
return pending.isEmpty() && dropped.isEmpty();
}
}

View File

@@ -26,6 +26,7 @@ import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor.LATEST;
@@ -182,15 +183,8 @@ public final class GroupsV1MigrationUtil {
return null;
}
List<RecipientId> pendingRecipients = Stream.of(DecryptedGroupUtil.pendingToUuidList(decryptedGroup.getPendingMembersList()))
.map(uuid -> Recipient.externalPush(context, uuid, null, false))
.filterNot(Recipient::isSelf)
.map(Recipient::getId)
.toList();
Log.i(TAG, "[Local] Migrating group over to the version we were added to: V" + decryptedGroup.getRevision());
DatabaseFactory.getGroupDatabase(context).migrateToV2(gv1Id, decryptedGroup);
DatabaseFactory.getSmsDatabase(context).insertGroupV1MigrationEvents(groupRecipient.getId(), threadId, pendingRecipients);
DatabaseFactory.getGroupDatabase(context).migrateToV2(threadId, gv1Id, decryptedGroup);
Log.i(TAG, "[Local] Applying all changes since V" + decryptedGroup.getRevision());
try {

View File

@@ -15,13 +15,12 @@ import androidx.lifecycle.ViewModelProviders;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.BottomSheetUtil;
import org.thoughtcrime.securesms.util.ThemeUtil;
import java.util.ArrayList;
import java.util.List;
/**
@@ -30,16 +29,19 @@ import java.util.List;
*/
public final class GroupsV1MigrationInfoBottomSheetDialogFragment extends BottomSheetDialogFragment {
private static final String KEY_PENDING = "pending";
private static final String KEY_MEMBERSHIP_CHANGE = "membership_change";
private GroupsV1MigrationInfoViewModel viewModel;
private GroupMemberListView pendingList;
private TextView pendingTitle;
private View pendingContainer;
private GroupMemberListView droppedList;
private TextView droppedTitle;
private View droppedContainer;
public static void showForLearnMore(@NonNull FragmentManager manager, @NonNull List<RecipientId> pendingRecipients) {
public static void show(@NonNull FragmentManager manager, @NonNull GroupMigrationMembershipChange membershipChange) {
Bundle args = new Bundle();
args.putParcelableArrayList(KEY_PENDING, new ArrayList<>(pendingRecipients));
args.putString(KEY_MEMBERSHIP_CHANGE, membershipChange.serialize());
GroupsV1MigrationInfoBottomSheetDialogFragment fragment = new GroupsV1MigrationInfoBottomSheetDialogFragment();
fragment.setArguments(args);
@@ -66,12 +68,16 @@ public final class GroupsV1MigrationInfoBottomSheetDialogFragment extends Bottom
this.pendingContainer = view.findViewById(R.id.gv1_learn_more_pending_container);
this.pendingTitle = view.findViewById(R.id.gv1_learn_more_pending_title);
this.pendingList = view.findViewById(R.id.gv1_learn_more_pending_list);
this.droppedContainer = view.findViewById(R.id.gv1_learn_more_dropped_container);
this.droppedTitle = view.findViewById(R.id.gv1_learn_more_dropped_title);
this.droppedList = view.findViewById(R.id.gv1_learn_more_dropped_list);
//noinspection ConstantConditions
List<RecipientId> pending = getArguments().getParcelableArrayList(KEY_PENDING);
GroupMigrationMembershipChange membershipChange = GroupMigrationMembershipChange.deserialize(getArguments().getString(KEY_MEMBERSHIP_CHANGE));
this.viewModel = ViewModelProviders.of(this, new GroupsV1MigrationInfoViewModel.Factory(pending)).get(GroupsV1MigrationInfoViewModel.class);
this.viewModel = ViewModelProviders.of(this, new GroupsV1MigrationInfoViewModel.Factory(membershipChange)).get(GroupsV1MigrationInfoViewModel.class);
viewModel.getPendingMembers().observe(getViewLifecycleOwner(), this::onPendingMembersChanged);
viewModel.getDroppedMembers().observe(getViewLifecycleOwner(), this::onDroppedMembersChanged);
view.findViewById(R.id.gv1_learn_more_ok_button).setOnClickListener(v -> dismiss());
}
@@ -82,7 +88,10 @@ public final class GroupsV1MigrationInfoBottomSheetDialogFragment extends Bottom
}
private void onPendingMembersChanged(@NonNull List<Recipient> pendingMembers) {
if (pendingMembers.size() > 0) {
if (pendingMembers.size() == 1 && pendingMembers.get(0).isSelf()) {
pendingContainer.setVisibility(View.VISIBLE);
pendingTitle.setText(R.string.GroupsV1MigrationLearnMore_you_will_need_to_accept_an_invite_to_join_this_group_again);
} else if (pendingMembers.size() > 0) {
pendingContainer.setVisibility(View.VISIBLE);
pendingTitle.setText(getResources().getQuantityText(R.plurals.GroupsV1MigrationLearnMore_these_members_will_need_to_accept_an_invite, pendingMembers.size()));
pendingList.setDisplayOnlyMembers(pendingMembers);
@@ -90,4 +99,14 @@ public final class GroupsV1MigrationInfoBottomSheetDialogFragment extends Bottom
pendingContainer.setVisibility(View.GONE);
}
}
private void onDroppedMembersChanged(@NonNull List<Recipient> droppedMembers) {
if (droppedMembers.size() > 0) {
droppedContainer.setVisibility(View.VISIBLE);
droppedTitle.setText(getResources().getQuantityText(R.plurals.GroupsV1MigrationLearnMore_these_members_were_removed_from_the_group, droppedMembers.size()));
droppedList.setDisplayOnlyMembers(droppedMembers);
} else {
droppedContainer.setVisibility(View.GONE);
}
}
}

View File

@@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
@@ -15,12 +16,18 @@ import java.util.List;
class GroupsV1MigrationInfoViewModel extends ViewModel {
private final MutableLiveData<List<Recipient>> pendingMembers;
private final MutableLiveData<List<Recipient>> droppedMembers;
private GroupsV1MigrationInfoViewModel(@NonNull List<RecipientId> pendingMembers) {
private GroupsV1MigrationInfoViewModel(@NonNull GroupMigrationMembershipChange membershipChange) {
this.pendingMembers = new MutableLiveData<>();
this.droppedMembers = new MutableLiveData<>();
SignalExecutors.BOUNDED.execute(() -> {
this.pendingMembers.postValue(Recipient.resolvedList(pendingMembers));
this.pendingMembers.postValue(Recipient.resolvedList(membershipChange.getPending()));
});
SignalExecutors.BOUNDED.execute(() -> {
this.droppedMembers.postValue(Recipient.resolvedList(membershipChange.getDropped()));
});
}
@@ -28,17 +35,21 @@ class GroupsV1MigrationInfoViewModel extends ViewModel {
return pendingMembers;
}
@NonNull LiveData<List<Recipient>> getDroppedMembers() {
return droppedMembers;
}
static class Factory extends ViewModelProvider.NewInstanceFactory {
private final List<RecipientId> pendingMembers;
private final GroupMigrationMembershipChange membershipChange;
Factory(List<RecipientId> pendingMembers) {
this.pendingMembers = pendingMembers;
Factory(@NonNull GroupMigrationMembershipChange membershipChange) {
this.membershipChange = membershipChange;
}
@Override
public @NonNull<T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return modelClass.cast(new GroupsV1MigrationInfoViewModel(pendingMembers));
return modelClass.cast(new GroupsV1MigrationInfoViewModel(membershipChange));
}
}
}