kopia lustrzana https://github.com/ryukoposting/Signal-Android
Refactor how we handle GV1->GV2 migration suggestions.
rodzic
7864c8ceb4
commit
f4c723cc60
|
@ -16,7 +16,7 @@ public class GroupsV1MigrationSuggestionsReminder extends Reminder {
|
||||||
public GroupsV1MigrationSuggestionsReminder(@NonNull Context context, @NonNull List<RecipientId> suggestions) {
|
public GroupsV1MigrationSuggestionsReminder(@NonNull Context context, @NonNull List<RecipientId> suggestions) {
|
||||||
super(null, context.getResources().getQuantityString(R.plurals.GroupsV1MigrationSuggestionsReminder_members_couldnt_be_added_to_the_new_group, suggestions.size(), suggestions.size()));
|
super(null, context.getResources().getQuantityString(R.plurals.GroupsV1MigrationSuggestionsReminder_members_couldnt_be_added_to_the_new_group, suggestions.size(), suggestions.size()));
|
||||||
addAction(new Action(context.getResources().getQuantityString(R.plurals.GroupsV1MigrationSuggestionsReminder_add_members, suggestions.size()), R.id.reminder_action_gv1_suggestion_add_members));
|
addAction(new Action(context.getResources().getQuantityString(R.plurals.GroupsV1MigrationSuggestionsReminder_add_members, suggestions.size()), R.id.reminder_action_gv1_suggestion_add_members));
|
||||||
addAction(new Action(context.getResources().getString(R.string.GroupsV1MigrationSuggestionsReminder_not_now), R.id.reminder_action_gv1_suggestion_not_now));
|
addAction(new Action(context.getResources().getString(R.string.GroupsV1MigrationSuggestionsReminder_no_thanks), R.id.reminder_action_gv1_suggestion_no_thanks));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1782,8 +1782,8 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||||
reminderView.get().setOnActionClickListener(actionId -> {
|
reminderView.get().setOnActionClickListener(actionId -> {
|
||||||
if (actionId == R.id.reminder_action_gv1_suggestion_add_members) {
|
if (actionId == R.id.reminder_action_gv1_suggestion_add_members) {
|
||||||
GroupsV1MigrationSuggestionsDialog.show(this, recipient.get().requireGroupId().requireV2(), gv1MigrationSuggestions);
|
GroupsV1MigrationSuggestionsDialog.show(this, recipient.get().requireGroupId().requireV2(), gv1MigrationSuggestions);
|
||||||
} else if (actionId == R.id.reminder_action_gv1_suggestion_not_now) {
|
} else if (actionId == R.id.reminder_action_gv1_suggestion_no_thanks) {
|
||||||
groupViewModel.onSuggestedMembersBannerDismissed(recipient.get().requireGroupId());
|
groupViewModel.onSuggestedMembersBannerDismissed(recipient.get().requireGroupId(), gv1MigrationSuggestions);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
reminderView.get().setOnDismissListener(() -> {
|
reminderView.get().setOnDismissListener(() -> {
|
||||||
|
|
|
@ -31,13 +31,11 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.AsynchronousCallback;
|
import org.thoughtcrime.securesms.util.AsynchronousCallback;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.SetUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
final class ConversationGroupViewModel extends ViewModel {
|
final class ConversationGroupViewModel extends ViewModel {
|
||||||
|
@ -83,10 +81,10 @@ final class ConversationGroupViewModel extends ViewModel {
|
||||||
liveRecipient.setValue(recipient);
|
liveRecipient.setValue(recipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSuggestedMembersBannerDismissed(@NonNull GroupId groupId) {
|
void onSuggestedMembersBannerDismissed(@NonNull GroupId groupId, @NonNull List<RecipientId> suggestions) {
|
||||||
SignalExecutors.BOUNDED.execute(() -> {
|
SignalExecutors.BOUNDED.execute(() -> {
|
||||||
if (groupId.isV2()) {
|
if (groupId.isV2()) {
|
||||||
DatabaseFactory.getGroupDatabase(ApplicationDependencies.getApplication()).clearFormerV1Members(groupId.requireV2());
|
DatabaseFactory.getGroupDatabase(ApplicationDependencies.getApplication()).removeUnmigratedV1Members(groupId.requireV2(), suggestions);
|
||||||
liveRecipient.postValue(liveRecipient.getValue());
|
liveRecipient.postValue(liveRecipient.getValue());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -177,9 +175,9 @@ final class ConversationGroupViewModel extends ViewModel {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<RecipientId> difference = SetUtil.difference(record.getFormerV1Members(), record.getMembers());
|
return Stream.of(record.getUnmigratedV1Members())
|
||||||
|
.filterNot(m -> record.getMembers().contains(m))
|
||||||
return Stream.of(Recipient.resolvedList(difference))
|
.map(Recipient::resolved)
|
||||||
.filter(GroupsV1MigrationUtil::isAutoMigratable)
|
.filter(GroupsV1MigrationUtil::isAutoMigratable)
|
||||||
.map(Recipient::getId)
|
.map(Recipient::getId)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
|
@ -15,8 +15,10 @@ import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.signal.storageservice.protos.groups.AccessControl;
|
import org.signal.storageservice.protos.groups.AccessControl;
|
||||||
|
import org.signal.storageservice.protos.groups.GroupChange;
|
||||||
import org.signal.storageservice.protos.groups.Member;
|
import org.signal.storageservice.protos.groups.Member;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||||
|
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||||
import org.signal.zkgroup.InvalidInputException;
|
import org.signal.zkgroup.InvalidInputException;
|
||||||
import org.signal.zkgroup.groups.GroupMasterKey;
|
import org.signal.zkgroup.groups.GroupMasterKey;
|
||||||
|
@ -33,6 +35,7 @@ import org.thoughtcrime.securesms.util.SqlUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
||||||
|
import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||||
|
|
||||||
|
@ -47,6 +50,7 @@ import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Trace
|
@Trace
|
||||||
|
@ -54,22 +58,22 @@ public final class GroupDatabase extends Database {
|
||||||
|
|
||||||
private static final String TAG = Log.tag(GroupDatabase.class);
|
private static final String TAG = Log.tag(GroupDatabase.class);
|
||||||
|
|
||||||
static final String TABLE_NAME = "groups";
|
static final String TABLE_NAME = "groups";
|
||||||
private static final String ID = "_id";
|
private static final String ID = "_id";
|
||||||
static final String GROUP_ID = "group_id";
|
static final String GROUP_ID = "group_id";
|
||||||
static final String RECIPIENT_ID = "recipient_id";
|
static final String RECIPIENT_ID = "recipient_id";
|
||||||
private static final String TITLE = "title";
|
private static final String TITLE = "title";
|
||||||
static final String MEMBERS = "members";
|
static final String MEMBERS = "members";
|
||||||
private static final String AVATAR_ID = "avatar_id";
|
private static final String AVATAR_ID = "avatar_id";
|
||||||
private static final String AVATAR_KEY = "avatar_key";
|
private static final String AVATAR_KEY = "avatar_key";
|
||||||
private static final String AVATAR_CONTENT_TYPE = "avatar_content_type";
|
private static final String AVATAR_CONTENT_TYPE = "avatar_content_type";
|
||||||
private static final String AVATAR_RELAY = "avatar_relay";
|
private static final String AVATAR_RELAY = "avatar_relay";
|
||||||
private static final String AVATAR_DIGEST = "avatar_digest";
|
private static final String AVATAR_DIGEST = "avatar_digest";
|
||||||
private static final String TIMESTAMP = "timestamp";
|
private static final String TIMESTAMP = "timestamp";
|
||||||
static final String ACTIVE = "active";
|
static final String ACTIVE = "active";
|
||||||
static final String MMS = "mms";
|
static final String MMS = "mms";
|
||||||
private static final String EXPECTED_V2_ID = "expected_v2_id";
|
private static final String EXPECTED_V2_ID = "expected_v2_id";
|
||||||
private static final String FORMER_V1_MEMBERS = "former_v1_members";
|
private static final String UNMIGRATED_V1_MEMBERS = "former_v1_members";
|
||||||
|
|
||||||
|
|
||||||
/* V2 Group columns */
|
/* V2 Group columns */
|
||||||
|
@ -80,24 +84,24 @@ public final class GroupDatabase extends Database {
|
||||||
/** Serialized {@link DecryptedGroup} protobuf */
|
/** Serialized {@link DecryptedGroup} protobuf */
|
||||||
private static final String V2_DECRYPTED_GROUP = "decrypted_group";
|
private static final String V2_DECRYPTED_GROUP = "decrypted_group";
|
||||||
|
|
||||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
|
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
|
||||||
GROUP_ID + " TEXT, " +
|
GROUP_ID + " TEXT, " +
|
||||||
RECIPIENT_ID + " INTEGER, " +
|
RECIPIENT_ID + " INTEGER, " +
|
||||||
TITLE + " TEXT, " +
|
TITLE + " TEXT, " +
|
||||||
MEMBERS + " TEXT, " +
|
MEMBERS + " TEXT, " +
|
||||||
AVATAR_ID + " INTEGER, " +
|
AVATAR_ID + " INTEGER, " +
|
||||||
AVATAR_KEY + " BLOB, " +
|
AVATAR_KEY + " BLOB, " +
|
||||||
AVATAR_CONTENT_TYPE + " TEXT, " +
|
AVATAR_CONTENT_TYPE + " TEXT, " +
|
||||||
AVATAR_RELAY + " TEXT, " +
|
AVATAR_RELAY + " TEXT, " +
|
||||||
TIMESTAMP + " INTEGER, " +
|
TIMESTAMP + " INTEGER, " +
|
||||||
ACTIVE + " INTEGER DEFAULT 1, " +
|
ACTIVE + " INTEGER DEFAULT 1, " +
|
||||||
AVATAR_DIGEST + " BLOB, " +
|
AVATAR_DIGEST + " BLOB, " +
|
||||||
MMS + " INTEGER DEFAULT 0, " +
|
MMS + " INTEGER DEFAULT 0, " +
|
||||||
V2_MASTER_KEY + " BLOB, " +
|
V2_MASTER_KEY + " BLOB, " +
|
||||||
V2_REVISION + " BLOB, " +
|
V2_REVISION + " BLOB, " +
|
||||||
V2_DECRYPTED_GROUP + " BLOB, " +
|
V2_DECRYPTED_GROUP + " BLOB, " +
|
||||||
EXPECTED_V2_ID + " TEXT DEFAULT NULL, " +
|
EXPECTED_V2_ID + " TEXT DEFAULT NULL, " +
|
||||||
FORMER_V1_MEMBERS + " TEXT DEFAULT NULL);";
|
UNMIGRATED_V1_MEMBERS + " TEXT DEFAULT NULL);";
|
||||||
|
|
||||||
public static final String[] CREATE_INDEXS = {
|
public static final String[] CREATE_INDEXS = {
|
||||||
"CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON " + TABLE_NAME + " (" + GROUP_ID + ");",
|
"CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON " + TABLE_NAME + " (" + GROUP_ID + ");",
|
||||||
|
@ -106,7 +110,7 @@ public final class GroupDatabase extends Database {
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final String[] GROUP_PROJECTION = {
|
private static final String[] GROUP_PROJECTION = {
|
||||||
GROUP_ID, RECIPIENT_ID, TITLE, MEMBERS, FORMER_V1_MEMBERS, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST,
|
GROUP_ID, RECIPIENT_ID, TITLE, MEMBERS, UNMIGRATED_V1_MEMBERS, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST,
|
||||||
TIMESTAMP, ACTIVE, MMS, V2_MASTER_KEY, V2_REVISION, V2_DECRYPTED_GROUP
|
TIMESTAMP, ACTIVE, MMS, V2_MASTER_KEY, V2_REVISION, V2_DECRYPTED_GROUP
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -162,9 +166,23 @@ public final class GroupDatabase extends Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearFormerV1Members(@NonNull GroupId.V2 id) {
|
/**
|
||||||
|
* Removes the specified members from the list of 'unmigrated V1 members' -- the list of members
|
||||||
|
* that were either dropped or had to be invited when migrating the group from V1->V2.
|
||||||
|
*/
|
||||||
|
public void removeUnmigratedV1Members(@NonNull GroupId.V2 id, @NonNull List<RecipientId> toRemove) {
|
||||||
|
Optional<GroupRecord> group = getGroup(id);
|
||||||
|
|
||||||
|
if (!group.isPresent()) {
|
||||||
|
Log.w(TAG, "Couldn't find the group!", new Throwable());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RecipientId> newUnmigrated = group.get().getUnmigratedV1Members();
|
||||||
|
newUnmigrated.removeAll(toRemove);
|
||||||
|
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.putNull(FORMER_V1_MEMBERS);
|
values.put(UNMIGRATED_V1_MEMBERS, newUnmigrated.isEmpty() ? null : RecipientId.toSerializedList(newUnmigrated));
|
||||||
|
|
||||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, GROUP_ID + " = ?", SqlUtil.buildArgs(id));
|
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, GROUP_ID + " = ?", SqlUtil.buildArgs(id));
|
||||||
|
|
||||||
|
@ -505,16 +523,15 @@ public final class GroupDatabase extends Database {
|
||||||
contentValues.put(V2_MASTER_KEY, groupMasterKey.serialize());
|
contentValues.put(V2_MASTER_KEY, groupMasterKey.serialize());
|
||||||
contentValues.putNull(EXPECTED_V2_ID);
|
contentValues.putNull(EXPECTED_V2_ID);
|
||||||
|
|
||||||
List<RecipientId> newMembers = Stream.of(DecryptedGroupUtil.membersToUuidList(decryptedGroup.getMembersList())).map(u -> RecipientId.from(u, null)).toList();
|
List<RecipientId> newMembers = uuidsToRecipientIds(DecryptedGroupUtil.membersToUuidList(decryptedGroup.getMembersList()));
|
||||||
List<RecipientId> pendingMembers = Stream.of(DecryptedGroupUtil.pendingToUuidList(decryptedGroup.getPendingMembersList())).map(u -> RecipientId.from(u, null)).toList();
|
List<RecipientId> pendingMembers = uuidsToRecipientIds(DecryptedGroupUtil.pendingToUuidList(decryptedGroup.getPendingMembersList()));
|
||||||
|
|
||||||
newMembers.addAll(pendingMembers);
|
newMembers.addAll(pendingMembers);
|
||||||
|
|
||||||
List<RecipientId> droppedMembers = new ArrayList<>(SetUtil.difference(record.getMembers(), newMembers));
|
List<RecipientId> droppedMembers = new ArrayList<>(SetUtil.difference(record.getMembers(), newMembers));
|
||||||
|
List<RecipientId> unmigratedMembers = Util.concatenatedList(pendingMembers, droppedMembers);
|
||||||
|
|
||||||
if (droppedMembers.size() > 0) {
|
contentValues.put(UNMIGRATED_V1_MEMBERS, unmigratedMembers.isEmpty() ? null : RecipientId.toSerializedList(unmigratedMembers));
|
||||||
contentValues.put(FORMER_V1_MEMBERS, RecipientId.toSerializedList(record.getMembers()));
|
|
||||||
}
|
|
||||||
|
|
||||||
int updated = db.update(TABLE_NAME, contentValues, GROUP_ID + " = ?", SqlUtil.buildArgs(groupIdV1.toString()));
|
int updated = db.update(TABLE_NAME, contentValues, GROUP_ID + " = ?", SqlUtil.buildArgs(groupIdV1.toString()));
|
||||||
|
|
||||||
|
@ -543,10 +560,31 @@ public final class GroupDatabase extends Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(@NonNull GroupId.V2 groupId, @NonNull DecryptedGroup decryptedGroup) {
|
public void update(@NonNull GroupId.V2 groupId, @NonNull DecryptedGroup decryptedGroup) {
|
||||||
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
||||||
RecipientId groupRecipientId = recipientDatabase.getOrInsertFromGroupId(groupId);
|
RecipientId groupRecipientId = recipientDatabase.getOrInsertFromGroupId(groupId);
|
||||||
String title = decryptedGroup.getTitle();
|
Optional<GroupRecord> existingGroup = getGroup(groupId);
|
||||||
ContentValues contentValues = new ContentValues();
|
String title = decryptedGroup.getTitle();
|
||||||
|
ContentValues contentValues = new ContentValues();
|
||||||
|
|
||||||
|
if (existingGroup.isPresent() && existingGroup.get().getUnmigratedV1Members().size() > 0 && existingGroup.get().isV2Group()) {
|
||||||
|
Set<RecipientId> unmigratedV1Members = new HashSet<>(existingGroup.get().getUnmigratedV1Members());
|
||||||
|
|
||||||
|
DecryptedGroupChange change = GroupChangeReconstruct.reconstructGroupChange(existingGroup.get().requireV2GroupProperties().getDecryptedGroup(), decryptedGroup);
|
||||||
|
|
||||||
|
List<RecipientId> addedMembers = uuidsToRecipientIds(DecryptedGroupUtil.membersToUuidList(change.getNewMembersList()));
|
||||||
|
List<RecipientId> removedMembers = uuidsToRecipientIds(DecryptedGroupUtil.removedMembersUuidList(change));
|
||||||
|
List<RecipientId> addedInvites = uuidsToRecipientIds(DecryptedGroupUtil.pendingToUuidList(change.getNewPendingMembersList()));
|
||||||
|
List<RecipientId> removedInvites = uuidsToRecipientIds(DecryptedGroupUtil.removedPendingMembersUuidList(change));
|
||||||
|
List<RecipientId> acceptedInvites = uuidsToRecipientIds(DecryptedGroupUtil.membersToUuidList(change.getPromotePendingMembersList()));
|
||||||
|
|
||||||
|
unmigratedV1Members.removeAll(addedMembers);
|
||||||
|
unmigratedV1Members.removeAll(removedMembers);
|
||||||
|
unmigratedV1Members.removeAll(addedInvites);
|
||||||
|
unmigratedV1Members.removeAll(removedInvites);
|
||||||
|
unmigratedV1Members.removeAll(acceptedInvites);
|
||||||
|
|
||||||
|
contentValues.put(UNMIGRATED_V1_MEMBERS, unmigratedV1Members.isEmpty() ? null : RecipientId.toSerializedList(unmigratedV1Members));
|
||||||
|
}
|
||||||
|
|
||||||
contentValues.put(TITLE, title);
|
contentValues.put(TITLE, title);
|
||||||
contentValues.put(V2_REVISION, decryptedGroup.getRevision());
|
contentValues.put(V2_REVISION, decryptedGroup.getRevision());
|
||||||
|
@ -688,16 +726,11 @@ public final class GroupDatabase extends Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
|
||||||
public boolean isPendingMember(@NonNull GroupId.Push groupId, @NonNull Recipient recipient) {
|
|
||||||
return getGroup(groupId).transform(g -> g.isPendingMember(recipient)).or(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String serializeV2GroupMembers(@NonNull DecryptedGroup decryptedGroup) {
|
private static List<RecipientId> uuidsToRecipientIds(@NonNull List<UUID> uuids) {
|
||||||
List<RecipientId> groupMembers = new ArrayList<>(decryptedGroup.getMembersCount());
|
List<RecipientId> groupMembers = new ArrayList<>(uuids.size());
|
||||||
|
|
||||||
for (DecryptedMember member : decryptedGroup.getMembersList()) {
|
for (UUID uuid : uuids) {
|
||||||
UUID uuid = UuidUtil.fromByteString(member.getUuid());
|
|
||||||
if (UuidUtil.UNKNOWN_UUID.equals(uuid)) {
|
if (UuidUtil.UNKNOWN_UUID.equals(uuid)) {
|
||||||
Log.w(TAG, "Seen unknown UUID in members list");
|
Log.w(TAG, "Seen unknown UUID in members list");
|
||||||
} else {
|
} else {
|
||||||
|
@ -707,7 +740,14 @@ public final class GroupDatabase extends Database {
|
||||||
|
|
||||||
Collections.sort(groupMembers);
|
Collections.sort(groupMembers);
|
||||||
|
|
||||||
return RecipientId.toSerializedList(groupMembers);
|
return groupMembers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String serializeV2GroupMembers(@NonNull DecryptedGroup decryptedGroup) {
|
||||||
|
List<UUID> uuids = DecryptedGroupUtil.membersToUuidList(decryptedGroup.getMembersList());
|
||||||
|
List<RecipientId> recipientIds = uuidsToRecipientIds(uuids);
|
||||||
|
|
||||||
|
return RecipientId.toSerializedList(recipientIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull List<GroupId.V2> getAllGroupV2Ids() {
|
public @NonNull List<GroupId.V2> getAllGroupV2Ids() {
|
||||||
|
@ -772,7 +812,7 @@ public final class GroupDatabase extends Database {
|
||||||
RecipientId.from(CursorUtil.requireString(cursor, RECIPIENT_ID)),
|
RecipientId.from(CursorUtil.requireString(cursor, RECIPIENT_ID)),
|
||||||
CursorUtil.requireString(cursor, TITLE),
|
CursorUtil.requireString(cursor, TITLE),
|
||||||
CursorUtil.requireString(cursor, MEMBERS),
|
CursorUtil.requireString(cursor, MEMBERS),
|
||||||
CursorUtil.requireString(cursor, FORMER_V1_MEMBERS),
|
CursorUtil.requireString(cursor, UNMIGRATED_V1_MEMBERS),
|
||||||
CursorUtil.requireLong(cursor, AVATAR_ID),
|
CursorUtil.requireLong(cursor, AVATAR_ID),
|
||||||
CursorUtil.requireBlob(cursor, AVATAR_KEY),
|
CursorUtil.requireBlob(cursor, AVATAR_KEY),
|
||||||
CursorUtil.requireString(cursor, AVATAR_CONTENT_TYPE),
|
CursorUtil.requireString(cursor, AVATAR_CONTENT_TYPE),
|
||||||
|
@ -798,7 +838,7 @@ public final class GroupDatabase extends Database {
|
||||||
private final RecipientId recipientId;
|
private final RecipientId recipientId;
|
||||||
private final String title;
|
private final String title;
|
||||||
private final List<RecipientId> members;
|
private final List<RecipientId> members;
|
||||||
private final List<RecipientId> formerV1Members;
|
private final List<RecipientId> unmigratedV1Members;
|
||||||
private final long avatarId;
|
private final long avatarId;
|
||||||
private final byte[] avatarKey;
|
private final byte[] avatarKey;
|
||||||
private final byte[] avatarDigest;
|
private final byte[] avatarDigest;
|
||||||
|
@ -812,7 +852,7 @@ public final class GroupDatabase extends Database {
|
||||||
@NonNull RecipientId recipientId,
|
@NonNull RecipientId recipientId,
|
||||||
String title,
|
String title,
|
||||||
String members,
|
String members,
|
||||||
String formerV1Members,
|
@Nullable String unmigratedV1Members,
|
||||||
long avatarId,
|
long avatarId,
|
||||||
byte[] avatarKey,
|
byte[] avatarKey,
|
||||||
String avatarContentType,
|
String avatarContentType,
|
||||||
|
@ -853,10 +893,10 @@ public final class GroupDatabase extends Database {
|
||||||
this.members = Collections.emptyList();
|
this.members = Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(formerV1Members)) {
|
if (!TextUtils.isEmpty(unmigratedV1Members)) {
|
||||||
this.formerV1Members = RecipientId.fromSerializedList(formerV1Members);
|
this.unmigratedV1Members = RecipientId.fromSerializedList(unmigratedV1Members);
|
||||||
} else {
|
} else {
|
||||||
this.formerV1Members = Collections.emptyList();
|
this.unmigratedV1Members = Collections.emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -876,8 +916,9 @@ public final class GroupDatabase extends Database {
|
||||||
return members;
|
return members;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull List<RecipientId> getFormerV1Members() {
|
/** V1 members that were lost during the V1->V2 migration */
|
||||||
return formerV1Members;
|
public @NonNull List<RecipientId> getUnmigratedV1Members() {
|
||||||
|
return unmigratedV1Members;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasAvatar() {
|
public boolean hasAvatar() {
|
||||||
|
|
|
@ -164,8 +164,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||||
private static final int GV1_MIGRATION_LAST_SEEN = 82;
|
private static final int GV1_MIGRATION_LAST_SEEN = 82;
|
||||||
private static final int VIEWED_RECEIPTS = 83;
|
private static final int VIEWED_RECEIPTS = 83;
|
||||||
private static final int CLEAN_UP_GV1_IDS = 84;
|
private static final int CLEAN_UP_GV1_IDS = 84;
|
||||||
|
private static final int GV1_MIGRATION_REFACTOR = 85;
|
||||||
|
|
||||||
private static final int DATABASE_VERSION = 84;
|
private static final int DATABASE_VERSION = 85;
|
||||||
private static final String DATABASE_NAME = "signal.db";
|
private static final String DATABASE_NAME = "signal.db";
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
@ -1234,6 +1235,15 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < GV1_MIGRATION_REFACTOR) {
|
||||||
|
ContentValues values = new ContentValues(1);
|
||||||
|
values.putNull("former_v1_members");
|
||||||
|
|
||||||
|
int count = db.update("groups", values, "former_v1_members NOT NULL", null);
|
||||||
|
|
||||||
|
Log.i(TAG, "Cleared former_v1_members for " + count + " rows");
|
||||||
|
}
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
|
|
|
@ -75,15 +75,15 @@ public final class GroupsV1MigrationSuggestionsDialog {
|
||||||
SimpleTask.run(SignalExecutors.UNBOUNDED, () -> {
|
SimpleTask.run(SignalExecutors.UNBOUNDED, () -> {
|
||||||
try {
|
try {
|
||||||
GroupManager.addMembers(fragmentActivity, groupId.requirePush(), suggestions);
|
GroupManager.addMembers(fragmentActivity, groupId.requirePush(), suggestions);
|
||||||
Log.i(TAG, "Successfully added members! Clearing former members.");
|
Log.i(TAG, "Successfully added members! Removing these dropped members from the list.");
|
||||||
DatabaseFactory.getGroupDatabase(fragmentActivity).clearFormerV1Members(groupId);
|
DatabaseFactory.getGroupDatabase(fragmentActivity).removeUnmigratedV1Members(groupId, suggestions);
|
||||||
return Result.SUCCESS;
|
return Result.SUCCESS;
|
||||||
} catch (IOException | GroupChangeBusyException e) {
|
} catch (IOException | GroupChangeBusyException e) {
|
||||||
Log.w(TAG, "Temporary failure.", e);
|
Log.w(TAG, "Temporary failure.", e);
|
||||||
return Result.NETWORK_ERROR;
|
return Result.NETWORK_ERROR;
|
||||||
} catch (GroupNotAMemberException | GroupInsufficientRightsException | MembershipNotSuitableForV2Exception | GroupChangeFailedException e) {
|
} catch (GroupNotAMemberException | GroupInsufficientRightsException | MembershipNotSuitableForV2Exception | GroupChangeFailedException e) {
|
||||||
Log.w(TAG, "Permanent failure! Clearing former members.", e);
|
Log.w(TAG, "Permanent failure! Removing these dropped members from the list.", e);
|
||||||
DatabaseFactory.getGroupDatabase(fragmentActivity).clearFormerV1Members(groupId);
|
DatabaseFactory.getGroupDatabase(fragmentActivity).removeUnmigratedV1Members(groupId, suggestions);
|
||||||
return Result.IMPOSSIBLE;
|
return Result.IMPOSSIBLE;
|
||||||
}
|
}
|
||||||
}, result -> {
|
}, result -> {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
@ -94,7 +95,7 @@ public class RecipientId implements Parcelable, Comparable<RecipientId> {
|
||||||
id = in.readLong();
|
id = in.readLong();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NonNull String toSerializedList(@NonNull List<RecipientId> ids) {
|
public static @NonNull String toSerializedList(@NonNull Collection<RecipientId> ids) {
|
||||||
return Util.join(Stream.of(ids).map(RecipientId::serialize).toList(), String.valueOf(DELIMITER));
|
return Util.join(Stream.of(ids).map(RecipientId::serialize).toList(), String.valueOf(DELIMITER));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<item name="reminder_action_update_now" type="id" />
|
<item name="reminder_action_update_now" type="id" />
|
||||||
<item name="reminder_action_review_join_requests" type="id" />
|
<item name="reminder_action_review_join_requests" type="id" />
|
||||||
|
|
||||||
<item name="reminder_action_gv1_suggestion_not_now" type="id" />
|
<item name="reminder_action_gv1_suggestion_no_thanks" type="id" />
|
||||||
<item name="reminder_action_gv1_suggestion_add_members" type="id" />
|
<item name="reminder_action_gv1_suggestion_add_members" type="id" />
|
||||||
|
|
||||||
<item name="reminder_action_gv1_initiation_not_now" type="id" />
|
<item name="reminder_action_gv1_initiation_not_now" type="id" />
|
||||||
|
|
|
@ -613,7 +613,7 @@
|
||||||
<item quantity="one">Add member</item>
|
<item quantity="one">Add member</item>
|
||||||
<item quantity="other">Add members</item>
|
<item quantity="other">Add members</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="GroupsV1MigrationSuggestionsReminder_not_now">Not now</string>
|
<string name="GroupsV1MigrationSuggestionsReminder_no_thanks">No thanks</string>
|
||||||
|
|
||||||
<!-- GroupsV1MigrationSuggestionsDialog -->
|
<!-- GroupsV1MigrationSuggestionsDialog -->
|
||||||
<plurals name="GroupsV1MigrationSuggestionsDialog_add_members_question">
|
<plurals name="GroupsV1MigrationSuggestionsDialog_add_members_question">
|
||||||
|
|
Ładowanie…
Reference in New Issue