From eff564ad8873cff157f3f8e6b148e7089a934b8c Mon Sep 17 00:00:00 2001 From: Alan Evans Date: Tue, 12 May 2020 15:09:47 -0300 Subject: [PATCH] Adapt message requests to support invite flow. --- .../conversation/ConversationActivity.java | 17 ++++- .../securesms/database/GroupDatabase.java | 16 ++++ .../securesms/database/ThreadDatabase.java | 45 ++++++++--- .../database/model/ThreadRecord.java | 13 +++- .../securesms/groups/GroupManager.java | 2 + .../securesms/jobs/ProfileKeySendJob.java | 7 ++ .../MessageRequestRepository.java | 74 +++++++++++++++---- .../MessageRequestViewModel.java | 6 +- .../MessageRequestsBottomView.java | 6 +- .../securesms/recipients/Recipient.java | 5 ++ .../securesms/sms/MessageSender.java | 3 + app/src/main/res/values/strings.xml | 2 + .../api/groupsv2/DecryptedGroupUtil.java | 6 ++ 13 files changed, 169 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 4644d77782..5ce8961565 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -153,6 +153,8 @@ import org.thoughtcrime.securesms.groups.GroupChangeFailedException; import org.thoughtcrime.securesms.groups.GroupInsufficientRightsException; import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.GroupNotAMemberException; +import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason; +import org.thoughtcrime.securesms.groups.ui.GroupErrors; import org.thoughtcrime.securesms.groups.ui.LeaveGroupDialog; import org.thoughtcrime.securesms.groups.ui.managegroup.ManageGroupActivity; import org.thoughtcrime.securesms.groups.ui.pendingmemberinvites.PendingMemberInvitesActivity; @@ -2190,6 +2192,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity return getRecipient() != null && getRecipient().isPushGroup(); } + private boolean isPushGroupV1Conversation() { + return getRecipient() != null && getRecipient().isPushV1Group(); + } + private boolean isSmsForced() { return sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms(); } @@ -2825,7 +2831,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override public void onMessageRequest(@NonNull MessageRequestViewModel viewModel) { - messageRequestBottomView.setAcceptOnClickListener(v -> viewModel.onAccept()); + messageRequestBottomView.setAcceptOnClickListener(v -> viewModel.onAccept(this::showGroupChangeErrorToast)); messageRequestBottomView.setDeleteOnClickListener(v -> onMessageRequestDeleteClicked(viewModel)); messageRequestBottomView.setBlockOnClickListener(v -> onMessageRequestBlockClicked(viewModel)); messageRequestBottomView.setUnblockOnClickListener(v -> onMessageRequestUnblockClicked(viewModel)); @@ -2844,6 +2850,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity }); } + private void showGroupChangeErrorToast(@NonNull GroupChangeFailureReason e) { + Toast.makeText(this, GroupErrors.getUserDisplayMessage(e), Toast.LENGTH_LONG).show(); + } + @Override public void handleReaction(@NonNull View maskTarget, @NonNull MessageRecord messageRecord, @@ -3005,9 +3015,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void presentMessageRequestDisplayState(@NonNull MessageRequestViewModel.DisplayState displayState) { - if (getIntent().hasExtra(TEXT_EXTRA) || getIntent().hasExtra(MEDIA_EXTRA) || getIntent().hasExtra(STICKER_EXTRA) || (isPushGroupConversation() && !isActiveGroup())) { + if (getIntent().hasExtra(TEXT_EXTRA) || getIntent().hasExtra(MEDIA_EXTRA) || getIntent().hasExtra(STICKER_EXTRA)) { Log.d(TAG, "[presentMessageRequestDisplayState] Have extra, so ignoring provided state."); messageRequestBottomView.setVisibility(View.GONE); + } else if (isPushGroupV1Conversation() && !isActiveGroup()) { + Log.d(TAG, "[presentMessageRequestDisplayState] Inactive push group V1, so ignoring provided state."); + messageRequestBottomView.setVisibility(View.GONE); } else { Log.d(TAG, "[presentMessageRequestDisplayState] " + displayState); switch (displayState) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java index 6c0b94f147..a146030dfc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -496,6 +496,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 Context context, @NonNull DecryptedGroup decryptedGroup) { List groupMembers = new ArrayList<>(decryptedGroup.getMembersCount()); @@ -707,6 +712,17 @@ public final class GroupDatabase extends Database { return id.isV1() ? GroupAccessControl.ALL_MEMBERS : GroupAccessControl.ONLY_ADMINS; } } + + boolean isPendingMember(@NonNull Recipient recipient) { + if (isV2Group()) { + Optional uuid = recipient.getUuid(); + if (uuid.isPresent()) { + return DecryptedGroupUtil.findPendingByUuid(requireV2GroupProperties().getDecryptedGroup().getPendingMembersList(), uuid.get()) + .isPresent(); + } + } + return false; + } } public static class V2GroupProperties { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index d53e6482e2..eb3f3a40c4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -31,6 +31,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import net.sqlcipher.database.SQLiteDatabase; +import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.contactshare.ContactUtil; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; @@ -51,6 +52,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil; import java.io.Closeable; import java.io.IOException; @@ -61,6 +63,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; public class ThreadDatabase extends Database { @@ -761,12 +764,21 @@ public class ThreadDatabase extends Database { RecipientId threadRecipientId = getRecipientIdForThreadId(record.getThreadId()); if (!messageRequestAccepted && threadRecipientId != null) { - boolean isPushGroup = Recipient.resolved(threadRecipientId).isPushGroup(); - if (isPushGroup) { - RecipientId recipientId = DatabaseFactory.getMmsSmsDatabase(context).getGroupAddedBy(record.getThreadId()); + Recipient resolved = Recipient.resolved(threadRecipientId); + if (resolved.isPushGroup()) { + if (resolved.isPushV2Group()) { + DecryptedGroup decryptedGroup = DatabaseFactory.getGroupDatabase(context).requireGroup(resolved.requireGroupId().requireV2()).requireV2GroupProperties().getDecryptedGroup(); + Optional inviter = DecryptedGroupUtil.findInviter(decryptedGroup.getPendingMembersList(), Recipient.self().getUuid().get()); - if (recipientId != null) { - return Extra.forGroupMessageRequest(recipientId); + RecipientId recipientId = inviter.isPresent() ? RecipientId.from(inviter.get(), null) : RecipientId.UNKNOWN; + + return Extra.forGroupV2invite(recipientId); + } else { + RecipientId recipientId = DatabaseFactory.getMmsSmsDatabase(context).getGroupAddedBy(record.getThreadId()); + + if (recipientId != null) { + return Extra.forGroupMessageRequest(recipientId); + } } } @@ -903,6 +915,7 @@ public class ThreadDatabase extends Database { @JsonProperty private final boolean isAlbum; @JsonProperty private final boolean isRemoteDelete; @JsonProperty private final boolean isMessageRequestAccepted; + @JsonProperty private final boolean isGv2Invite; @JsonProperty private final String groupAddedBy; public Extra(@JsonProperty("isRevealable") boolean isRevealable, @@ -910,6 +923,7 @@ public class ThreadDatabase extends Database { @JsonProperty("isAlbum") boolean isAlbum, @JsonProperty("isRemoteDelete") boolean isRemoteDelete, @JsonProperty("isMessageRequestAccepted") boolean isMessageRequestAccepted, + @JsonProperty("isGv2Invite") boolean isGv2Invite, @JsonProperty("groupAddedBy") String groupAddedBy) { this.isRevealable = isRevealable; @@ -917,31 +931,36 @@ public class ThreadDatabase extends Database { this.isAlbum = isAlbum; this.isRemoteDelete = isRemoteDelete; this.isMessageRequestAccepted = isMessageRequestAccepted; + this.isGv2Invite = isGv2Invite; this.groupAddedBy = groupAddedBy; } public static @NonNull Extra forViewOnce() { - return new Extra(true, false, false, false, true, null); + return new Extra(true, false, false, false, true, false, null); } public static @NonNull Extra forSticker() { - return new Extra(false, true, false, false, true, null); + return new Extra(false, true, false, false, true, false, null); } public static @NonNull Extra forAlbum() { - return new Extra(false, false, true, false, true, null); + return new Extra(false, false, true, false, true, false, null); } public static @NonNull Extra forRemoteDelete() { - return new Extra(false, false, false, true, true, null); + return new Extra(false, false, false, true, true, false, null); } public static @NonNull Extra forMessageRequest() { - return new Extra(false, false, false, false, false, null); + return new Extra(false, false, false, false, false, false, null); } public static @NonNull Extra forGroupMessageRequest(RecipientId recipientId) { - return new Extra(false, false, false, false, false, recipientId.serialize()); + return new Extra(false, false, false, false, false, false, recipientId.serialize()); + } + + public static @NonNull Extra forGroupV2invite(RecipientId recipientId) { + return new Extra(false, false, false, false, false, true, recipientId.serialize()); } public boolean isViewOnce() { @@ -964,6 +983,10 @@ public class ThreadDatabase extends Database { return isMessageRequestAccepted; } + public boolean isGv2Invite() { + return isGv2Invite; + } + public @Nullable String getGroupAddedBy() { return groupAddedBy; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 325ed0143a..dcb1785a6f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -19,13 +19,14 @@ package org.thoughtcrime.securesms.database.model; import android.content.Context; import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; import android.text.style.StyleSpan; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; @@ -79,7 +80,9 @@ public class ThreadRecord extends DisplayRecord { @Override public SpannableString getDisplayBody(@NonNull Context context) { if (getGroupAddedBy() != null) { - return emphasisAdded(context.getString(R.string.ThreadRecord_s_added_you_to_the_group, Recipient.live(getGroupAddedBy()).get().getDisplayName(context))); + return emphasisAdded(context.getString(isGv2Invite() ? R.string.ThreadRecord_s_invited_you_to_the_group + : R.string.ThreadRecord_s_added_you_to_the_group, + Recipient.live(getGroupAddedBy()).get().getDisplayName(context))); } else if (!isMessageRequestAccepted()) { return emphasisAdded(context.getString(R.string.ThreadRecord_message_request)); } else if (isGroupUpdate()) { @@ -194,6 +197,10 @@ public class ThreadRecord extends DisplayRecord { else return null; } + public boolean isGv2Invite() { + return extra != null && extra.isGv2Invite(); + } + public boolean isMessageRequestAccepted() { if (extra != null) return extra.isMessageRequestAccepted(); else return true; diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java index aab0cd6dc3..2f724538ff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java @@ -183,6 +183,8 @@ public final class GroupManager { { try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) { editor.acceptInvite(); + DatabaseFactory.getGroupDatabase(context) + .setActive(groupId, true); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java index 4a7238ba51..ef12c962d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java @@ -41,6 +41,9 @@ public class ProfileKeySendJob extends BaseJob { private final long threadId; private final List recipients; + /** + * Suitable for a 1:1 conversation or a GV1 group only. + */ @WorkerThread public static ProfileKeySendJob create(@NonNull Context context, long threadId) { Recipient conversationRecipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId); @@ -49,6 +52,10 @@ public class ProfileKeySendJob extends BaseJob { throw new AssertionError("We have a thread but no recipient!"); } + if (conversationRecipient.isPushV2Group()) { + throw new AssertionError("Do not send profile keys directly for GV2"); + } + List recipients = conversationRecipient.isGroup() ? Stream.of(conversationRecipient.getParticipants()).map(Recipient::getId).toList() : Stream.of(conversationRecipient.getId()).toList(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java index da0cbdb851..dfcc3b0fc4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java @@ -5,13 +5,22 @@ import android.content.Context; import androidx.annotation.NonNull; import androidx.core.util.Consumer; +import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.MessagingDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.groups.GroupChangeBusyException; +import org.thoughtcrime.securesms.groups.GroupChangeFailedException; +import org.thoughtcrime.securesms.groups.GroupInsufficientRightsException; +import org.thoughtcrime.securesms.groups.GroupManager; +import org.thoughtcrime.securesms.groups.GroupNotAMemberException; +import org.thoughtcrime.securesms.groups.ui.GroupChangeErrorCallback; +import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason; import org.thoughtcrime.securesms.jobs.MultiDeviceMessageRequestResponseJob; +import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.notifications.MarkReadReceiver; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.LiveRecipient; @@ -20,14 +29,18 @@ import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import org.whispersystems.libsignal.util.guava.Optional; +import java.io.IOException; import java.util.List; import java.util.concurrent.Executor; final class MessageRequestRepository { + private static final String TAG = Log.tag(MessageRequestRepository.class); + private final Context context; private final Executor executor; @@ -48,14 +61,24 @@ final class MessageRequestRepository { GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); Optional groupRecord = groupDatabase.getGroup(recipientId); onMemberCountLoaded.accept(groupRecord.transform(record -> { + if (record.isV2Group()) { + DecryptedGroup decryptedGroup = record.requireV2GroupProperties().getDecryptedGroup(); + return new GroupMemberCount(decryptedGroup.getMembersCount(), decryptedGroup.getPendingMembersCount()); + } else { return new GroupMemberCount(record.getMembers().size(), 0); + } }).or(GroupMemberCount.ZERO)); }); } void getMessageRequestState(@NonNull Recipient recipient, long threadId, @NonNull Consumer state) { executor.execute(() -> { - if (!RecipientUtil.isMessageRequestAccepted(context, threadId)) { + if (recipient.isPushV2Group()) { + boolean pendingMember = DatabaseFactory.getGroupDatabase(context) + .isPendingMember(recipient.requireGroupId().requireV2(), Recipient.self()); + state.accept(pendingMember ? MessageRequestState.UNACCEPTED + : MessageRequestState.ACCEPTED); + } else if (!RecipientUtil.isMessageRequestAccepted(context, threadId)) { state.accept(MessageRequestState.UNACCEPTED); } else if (RecipientUtil.isPreMessageRequestThread(context, threadId) && !RecipientUtil.isLegacyProfileSharingAccepted(recipient)) { state.accept(MessageRequestState.LEGACY); @@ -65,23 +88,46 @@ final class MessageRequestRepository { }); } - void acceptMessageRequest(@NonNull LiveRecipient liveRecipient, long threadId, @NonNull Runnable onMessageRequestAccepted) { + void acceptMessageRequest(@NonNull LiveRecipient liveRecipient, + long threadId, + @NonNull Runnable onMessageRequestAccepted, + @NonNull GroupChangeErrorCallback mainThreadError) + { + GroupChangeErrorCallback error = e -> Util.runOnMain(() -> mainThreadError.onError(e)); executor.execute(()-> { - RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); - recipientDatabase.setProfileSharing(liveRecipient.getId(), true); - liveRecipient.refresh(); + if (liveRecipient.get().isPushV2Group()) { + try { + Log.i(TAG, "GV2 accepting invite"); + GroupManager.acceptInvite(context, liveRecipient.get().requireGroupId().requireV2()); - List messageIds = DatabaseFactory.getThreadDatabase(context) - .setEntireThreadRead(threadId); - MessageNotifier.updateNotification(context); - MarkReadReceiver.process(context, messageIds); + RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); + recipientDatabase.setProfileSharing(liveRecipient.getId(), true); - if (TextSecurePreferences.isMultiDevice(context)) { - ApplicationDependencies.getJobManager().add(MultiDeviceMessageRequestResponseJob.forAccept(liveRecipient.getId())); + onMessageRequestAccepted.run(); + } catch (GroupInsufficientRightsException e) { + Log.w(TAG, e); + error.onError(GroupChangeFailureReason.NO_RIGHTS); + } catch (GroupChangeBusyException | GroupChangeFailedException | GroupNotAMemberException | IOException e) { + Log.w(TAG, e); + error.onError(GroupChangeFailureReason.OTHER); + } + } else { + RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); + recipientDatabase.setProfileSharing(liveRecipient.getId(), true); + + MessageSender.sendProfileKey(context, threadId); + + List messageIds = DatabaseFactory.getThreadDatabase(context) + .setEntireThreadRead(threadId); + MessageNotifier.updateNotification(context); + MarkReadReceiver.process(context, messageIds); + + if (TextSecurePreferences.isMultiDevice(context)) { + ApplicationDependencies.getJobManager().add(MultiDeviceMessageRequestResponseJob.forAccept(liveRecipient.getId())); + } + + onMessageRequestAccepted.run(); } - - MessageSender.sendProfileKey(context, threadId); - onMessageRequestAccepted.run(); }); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestViewModel.java index d416da07c3..731f324451 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestViewModel.java @@ -11,6 +11,7 @@ import androidx.lifecycle.Transformations; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; +import org.thoughtcrime.securesms.groups.ui.GroupChangeErrorCallback; import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; @@ -89,10 +90,11 @@ public class MessageRequestViewModel extends ViewModel { } @MainThread - public void onAccept() { + public void onAccept(@NonNull GroupChangeErrorCallback error) { repository.acceptMessageRequest(liveRecipient, threadId, () -> { status.postValue(Status.ACCEPTED); - }); + }, + error); } @MainThread diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsBottomView.java b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsBottomView.java index d333486d41..24a5e85359 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsBottomView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsBottomView.java @@ -65,7 +65,11 @@ public class MessageRequestsBottomView extends ConstraintLayout { blockedButtons.setVisibility(VISIBLE); } else { if (recipient.isGroup()) { - question.setText(HtmlCompat.fromHtml(getContext().getString(R.string.MessageRequestBottomView_do_you_want_to_join_the_group_s_they_wont_know_youve_seen_their_messages_until_you_accept, HtmlUtil.bold(recipient.getDisplayName(getContext()))), 0)); + if (recipient.isPushV2Group()) { + question.setText(HtmlCompat.fromHtml(getContext().getString(R.string.MessageRequestBottomView_you_were_invited_to_join_the_group_s, HtmlUtil.bold(recipient.getDisplayName(getContext()))), 0)); + } else { + question.setText(HtmlCompat.fromHtml(getContext().getString(R.string.MessageRequestBottomView_do_you_want_to_join_the_group_s_they_wont_know_youve_seen_their_messages_until_you_accept, HtmlUtil.bold(recipient.getDisplayName(getContext()))), 0)); + } } else { question.setText(HtmlCompat.fromHtml(getContext().getString(R.string.MessageRequestBottomView_do_you_want_to_let_s_message_you_they_wont_know_youve_seen_their_messages_until_you_accept, HtmlUtil.bold(recipient.getDisplayName(getContext()))), 0)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java index 5bd212bca1..7ad8b1026a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java @@ -603,6 +603,11 @@ public class Recipient { return groupId != null && groupId.isPush(); } + public boolean isPushV1Group() { + GroupId groupId = resolve().groupId; + return groupId != null && groupId.isV1(); + } + public boolean isPushV2Group() { GroupId groupId = resolve().groupId; return groupId != null && groupId.isV2(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/sms/MessageSender.java b/app/src/main/java/org/thoughtcrime/securesms/sms/MessageSender.java index fff1a27e6b..ce5ab20eb3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sms/MessageSender.java @@ -83,6 +83,9 @@ public class MessageSender { private static final String TAG = MessageSender.class.getSimpleName(); + /** + * Suitable for a 1:1 conversation or a GV1 group only. + */ @WorkerThread public static void sendProfileKey(final Context context, final long threadId) { ApplicationDependencies.getJobManager().add(ProfileKeySendJob.create(context, threadId)); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0e0839ca3b..9b1a1a6261 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -774,6 +774,7 @@ Unblock Do you want to let %1$s message you? They won\'t know you\'ve seen their messages until you accept. Do you want to join the group %1$s? They won\'t know you\'ve seen their messages until you accept. + You were invited to join the group %1$s. Do you want to let members of this group message you? Unblock %1$s to message and call each other. Unblock to allow group members to add you to this group again. Member of %1$s @@ -1087,6 +1088,7 @@ Message could not be processed Message Request %1$s added you to the group + %1$s invited you to the group Signal update diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtil.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtil.java index 13c2cb373a..2a2ba43ece 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtil.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupUtil.java @@ -252,6 +252,12 @@ public final class DecryptedGroupUtil { return -1; } + public static Optional findInviter(List pendingMembersList, UUID uuid) { + return Optional.fromNullable(findPendingByUuid(pendingMembersList, uuid).transform(DecryptedPendingMember::getAddedByUuid) + .transform(UuidUtil::fromByteStringOrNull) + .orNull()); + } + public static class NotAbleToApplyChangeException extends Throwable { } }