From de7f103130569dab6009146e8d31d28b123d6e0f Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Mon, 12 Oct 2020 11:45:03 -0400 Subject: [PATCH] Add support for modern profile sharing. --- .../conversation/ConversationActivity.java | 2 +- .../jobs/MultiDeviceReadUpdateJob.java | 4 +- .../MessageRequestRepository.java | 9 +++- .../MessageRequestViewModel.java | 44 ++++++++++++++----- .../MessageRequestsBottomView.java | 37 +++++++++++++--- .../securesms/recipients/RecipientUtil.java | 2 +- .../securesms/util/FeatureFlags.java | 9 +++- .../res/layout/message_request_bottom_bar.xml | 2 +- app/src/main/res/values/strings.xml | 4 ++ 9 files changed, 88 insertions(+), 25 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 33c3b4633..8beb3c9ca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -3295,7 +3295,7 @@ public class ConversationActivity extends PassphraseRequiredActivity groupShareProfileView.get().setVisibility(View.GONE); } break; - case DISPLAY_LEGACY: + case DISPLAY_PRE_MESSAGE_REQUEST: if (recipient.get().isGroup()) { groupShareProfileView.get().setRecipient(recipient.get()); groupShareProfileView.get().setVisibility(View.VISIBLE); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java index 25455f91f..a5606d622 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java @@ -90,7 +90,9 @@ public class MultiDeviceReadUpdateJob extends BaseJob { for (SerializableSyncMessageId messageId : messageIds) { Recipient recipient = Recipient.resolved(RecipientId.from(messageId.recipientId)); - readMessages.add(new ReadMessage(RecipientUtil.toSignalServiceAddress(context, recipient), messageId.timestamp)); + if (!recipient.isGroup()) { + readMessages.add(new ReadMessage(RecipientUtil.toSignalServiceAddress(context, recipient), messageId.timestamp)); + } } SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); 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 522baa6ed..9b25d6026 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java @@ -26,6 +26,7 @@ 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.FeatureFlags; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import org.whispersystems.libsignal.util.guava.Optional; @@ -86,9 +87,11 @@ final class MessageRequestRepository { } } + return MessageRequestState.REQUIRED; + } else if (FeatureFlags.modernProfileSharing() && !recipient.isPushV2Group() && !recipient.isProfileSharing()) { return MessageRequestState.REQUIRED; } else if (RecipientUtil.isPreMessageRequestThread(context, threadId) && !RecipientUtil.isLegacyProfileSharingAccepted(recipient)) { - return MessageRequestState.LEGACY; + return MessageRequestState.PRE_MESSAGE_REQUEST; } else { return MessageRequestState.NOT_REQUIRED; } @@ -230,6 +233,7 @@ final class MessageRequestRepository { }); } + @WorkerThread boolean isPendingMember(@NonNull GroupId.V2 groupId) { return DatabaseFactory.getGroupDatabase(context).isPendingMember(groupId, Recipient.self()); } @@ -248,6 +252,7 @@ final class MessageRequestRepository { /** Explicit message request permission is required. */ REQUIRED, - LEGACY + /** This conversation existed before message requests and needs the old UI */ + PRE_MESSAGE_REQUEST } } 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 3538894ac..60bd9dcb3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestViewModel.java @@ -12,11 +12,15 @@ import androidx.lifecycle.Transformations; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason; import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.recipients.RecipientUtil; +import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.SingleLiveEvent; import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.util.livedata.LiveDataTriple; @@ -168,16 +172,20 @@ public class MessageRequestViewModel extends ViewModel { } else { return new MessageData(recipient, MessageClass.BLOCKED_INDIVIDUAL); } - } else if (recipient.isGroup()) { - if (recipient.isPushV2Group()) { - if (repository.isPendingMember(recipient.requireGroupId().requireV2())) { - return new MessageData(recipient, MessageClass.GROUP_V2_INVITE); - } else { - return new MessageData(recipient, MessageClass.GROUP_V2_ADD); - } + } else if (recipient.isPushV2Group()) { + if (repository.isPendingMember(recipient.requireGroupId().requireV2())) { + return new MessageData(recipient, MessageClass.GROUP_V2_INVITE); } else { - return new MessageData(recipient, MessageClass.GROUP_V1); + return new MessageData(recipient, MessageClass.GROUP_V2_ADD); } + } else if (isLegacyThread(recipient)) { + if (recipient.isGroup()) { + return new MessageData(recipient, MessageClass.LEGACY_GROUP_V1); + } else { + return new MessageData(recipient, MessageClass.LEGACY_INDIVIDUAL); + } + } else if (recipient.isGroup()) { + return new MessageData(recipient, MessageClass.GROUP_V1); } else { return new MessageData(recipient, MessageClass.INDIVIDUAL); } @@ -198,13 +206,23 @@ public class MessageRequestViewModel extends ViewModel { case REQUIRED: displayState.postValue(DisplayState.DISPLAY_MESSAGE_REQUEST); break; - case LEGACY: - displayState.postValue(DisplayState.DISPLAY_LEGACY); + case PRE_MESSAGE_REQUEST: + displayState.postValue(DisplayState.DISPLAY_PRE_MESSAGE_REQUEST); break; } }); } + @WorkerThread + private boolean isLegacyThread(@NonNull Recipient recipient) { + Context context = ApplicationDependencies.getApplication(); + Long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient.getId()); + + return FeatureFlags.modernProfileSharing() && + threadId != null && + (RecipientUtil.hasSentMessageInThread(context, threadId) || RecipientUtil.isPreMessageRequestThread(context, threadId)); + } + public static class RecipientInfo { @Nullable private final Recipient recipient; @NonNull private final GroupMemberCount groupMemberCount; @@ -246,12 +264,16 @@ public class MessageRequestViewModel extends ViewModel { } public enum DisplayState { - DISPLAY_MESSAGE_REQUEST, DISPLAY_LEGACY, DISPLAY_NONE + DISPLAY_MESSAGE_REQUEST, DISPLAY_PRE_MESSAGE_REQUEST, DISPLAY_NONE } public enum MessageClass { BLOCKED_INDIVIDUAL, BLOCKED_GROUP, + /** An individual conversation that existed pre-message-requests but doesn't have profile sharing enabled */ + LEGACY_INDIVIDUAL, + /** A V1 group conversation that existed pre-message-requests but doesn't have profile sharing enabled */ + LEGACY_GROUP_V1, GROUP_V1, GROUP_V2_INVITE, GROUP_V2_ADD, 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 e7e4a7c8b..6f101868c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsBottomView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsBottomView.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.messagerequests; import android.content.Context; import android.util.AttributeSet; import android.view.View; +import android.widget.Button; import android.widget.TextView; import androidx.annotation.NonNull; @@ -12,20 +13,22 @@ import androidx.core.text.HtmlCompat; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.Debouncer; import org.thoughtcrime.securesms.util.HtmlUtil; +import org.thoughtcrime.securesms.util.views.LearnMoreTextView; public class MessageRequestsBottomView extends ConstraintLayout { private final Debouncer showProgressDebouncer = new Debouncer(250); - private TextView question; - private View accept; - private View block; - private View delete; - private View bigDelete; - private View bigUnblock; - private View busyIndicator; + private LearnMoreTextView question; + private Button accept; + private View block; + private View delete; + private View bigDelete; + private View bigUnblock; + private View busyIndicator; private Group normalButtons; private Group blockedButtons; @@ -63,6 +66,9 @@ public class MessageRequestsBottomView extends ConstraintLayout { public void setMessageData(@NonNull MessageRequestViewModel.MessageData messageData) { Recipient recipient = messageData.getRecipient(); + question.setLearnMoreVisible(false); + question.setOnLinkClickListener(null); + switch (messageData.getMessageClass()) { case BLOCKED_INDIVIDUAL: question.setText(HtmlCompat.fromHtml(getContext().getString(R.string.MessageRequestBottomView_do_you_want_to_let_s_message_you_wont_receive_any_messages_until_you_unblock_them, @@ -73,19 +79,36 @@ public class MessageRequestsBottomView extends ConstraintLayout { question.setText(R.string.MessageRequestBottomView_unblock_this_group_and_share_your_name_and_photo_with_its_members); setActiveInactiveGroups(blockedButtons, normalButtons); break; + case LEGACY_INDIVIDUAL: + question.setText(getContext().getString(R.string.MessageRequestBottomView_continue_your_conversation_with_s_and_share_your_name_and_photo, recipient.getShortDisplayName(getContext()))); + question.setLearnMoreVisible(true); + question.setOnLinkClickListener(v -> CommunicationActions.openBrowserLink(getContext(), getContext().getString(R.string.MessageRequestBottomView_legacy_learn_more_url))); + setActiveInactiveGroups(normalButtons, blockedButtons); + accept.setText(R.string.MessageRequestBottomView_continue); + break; + case LEGACY_GROUP_V1: + question.setText(R.string.MessageRequestBottomView_continue_your_conversation_with_this_group_and_share_your_name_and_photo); + question.setLearnMoreVisible(true); + question.setOnLinkClickListener(v -> CommunicationActions.openBrowserLink(getContext(), getContext().getString(R.string.MessageRequestBottomView_legacy_learn_more_url))); + setActiveInactiveGroups(normalButtons, blockedButtons); + accept.setText(R.string.MessageRequestBottomView_continue); + break; case GROUP_V1: case GROUP_V2_INVITE: question.setText(R.string.MessageRequestBottomView_do_you_want_to_join_this_group_they_wont_know_youve_seen_their_messages_until_you_accept); setActiveInactiveGroups(normalButtons, blockedButtons); + accept.setText(R.string.MessageRequestBottomView_accept); break; case GROUP_V2_ADD: question.setText(R.string.MessageRequestBottomView_join_this_group_they_wont_know_youve_seen_their_messages_until_you_accept); setActiveInactiveGroups(normalButtons, blockedButtons); + accept.setText(R.string.MessageRequestBottomView_accept); break; case INDIVIDUAL: 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.getShortDisplayName(getContext()))), 0)); setActiveInactiveGroups(normalButtons, blockedButtons); + accept.setText(R.string.MessageRequestBottomView_accept); break; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java index 11fa1f1f5..270db4cdd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java @@ -280,7 +280,7 @@ public class RecipientUtil { } @WorkerThread - private static boolean hasSentMessageInThread(@NonNull Context context, long threadId) { + public static boolean hasSentMessageInThread(@NonNull Context context, long threadId) { return DatabaseFactory.getMmsSmsDatabase(context).getOutgoingSecureConversationCount(threadId) != 0; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index 645b5d392..11a2e088b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -58,6 +58,7 @@ public final class FeatureFlags { private static final String PHONE_NUMBER_PRIVACY_VERSION = "android.phoneNumberPrivacyVersion"; private static final String CLIENT_EXPIRATION = "android.clientExpiration"; public static final String RESEARCH_MEGAPHONE_1 = "research.megaphone.1"; + public static final String MODERN_PROFILE_SHARING = "android.modernProfileSharing"; /** * We will only store remote values for flags in this set. If you want a flag to be controllable @@ -74,7 +75,8 @@ public final class FeatureFlags { MENTIONS, VERIFY_V2, CLIENT_EXPIRATION, - RESEARCH_MEGAPHONE_1 + RESEARCH_MEGAPHONE_1, + MODERN_PROFILE_SHARING ); /** @@ -248,6 +250,11 @@ public final class FeatureFlags { return getVersionFlag(PHONE_NUMBER_PRIVACY_VERSION) == VersionFlag.ON; } + /** Whether or not to show the new profile sharing prompt for legacy conversations. */ + public static boolean modernProfileSharing() { + return getBoolean(MODERN_PROFILE_SHARING, false); + } + /** Only for rendering debug info. */ public static synchronized @NonNull Map getMemoryValues() { return new TreeMap<>(REMOTE_VALUES); diff --git a/app/src/main/res/layout/message_request_bottom_bar.xml b/app/src/main/res/layout/message_request_bottom_bar.xml index 247f90985..d2f21e79a 100644 --- a/app/src/main/res/layout/message_request_bottom_bar.xml +++ b/app/src/main/res/layout/message_request_bottom_bar.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> - Accept + Continue Delete Block Unblock Let %1$s message you and share your name and photo with them? They won\'t know you\'ve seen their message until you accept. Let %1$s message you and share your name and photo with them? You won\'t receive any messages until you unblock them. + Continue your conversation with this group and share your name and photo with its members? + Continue your conversation with %1$s and share your name and photo with them? Join this group and share your name and photo with its members? They won\'t know you\'ve seen their messages until you accept. Join this group? They won’t know you’ve seen their messages until you accept. Unblock this group and share your name and photo with its members? You won\'t receive any messages until you unblock them. + https://support.signal.org/hc/articles/360007459591 Member of %1$s Member of %1$s and %2$s Member of %1$s, %2$s, and %3$s