From 64ad9ec9ddde6233093d9eeb2f74943a23a27681 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Tue, 22 Aug 2017 11:51:01 -0700 Subject: [PATCH] Add group profile sharing logic // FREEBIE --- res/layout/conversation_activity.xml | 6 ++ ...tion_activity_group_share_profile_stub.xml | 6 ++ res/layout/profile_group_share_view.xml | 27 ++++++++ res/values/strings.xml | 8 +++ .../securesms/ConversationActivity.java | 42 +++++++---- .../securesms/ConversationFragment.java | 3 +- .../securesms/ConversationListActivity.java | 3 - .../securesms/jobs/PushGroupSendJob.java | 20 ++++-- .../profiles/GroupShareProfileView.java | 69 +++++++++++++++++++ .../securesms/recipients/Recipient.java | 3 + 10 files changed, 164 insertions(+), 23 deletions(-) create mode 100644 res/layout/conversation_activity_group_share_profile_stub.xml create mode 100644 res/layout/profile_group_share_view.xml create mode 100644 src/org/thoughtcrime/securesms/profiles/GroupShareProfileView.java diff --git a/res/layout/conversation_activity.xml b/res/layout/conversation_activity.xml index 5e5f06aef8..be65a5ce12 100644 --- a/res/layout/conversation_activity.xml +++ b/res/layout/conversation_activity.xml @@ -23,6 +23,12 @@ android:clipToPadding="false" android:clipChildren="false"> + + + diff --git a/res/layout/profile_group_share_view.xml b/res/layout/profile_group_share_view.xml new file mode 100644 index 0000000000..863e7cc2a1 --- /dev/null +++ b/res/layout/profile_group_share_view.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 186edc38d0..a884d724ca 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -327,6 +327,11 @@ Loading group details... You\'re already in the group. + + Share your profile name and photo with this group? + Do you want to make your profile name and photo visible to all current and future members of this group? + Make visible + Me @@ -959,6 +964,9 @@ Group name is now \'%1$s\'. + + Make your profile name and photo visible to this group? + Unlock diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index 5e8aa674b9..a46f7046b3 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -127,6 +127,7 @@ import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.notifications.MarkReadReceiver; import org.thoughtcrime.securesms.notifications.MessageNotifier; +import org.thoughtcrime.securesms.profiles.GroupShareProfileView; import org.thoughtcrime.securesms.providers.PersistentBlobProvider; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; @@ -206,20 +207,21 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private static final int PICK_GIF = 9; private static final int SMS_DEFAULT = 10; - private MasterSecret masterSecret; - protected ComposeText composeText; - private AnimatingToggle buttonToggle; - private SendButton sendButton; - private ImageButton attachButton; - protected ConversationTitleView titleView; - private TextView charactersLeft; - private ConversationFragment fragment; - private Button unblockButton; - private Button makeDefaultSmsButton; - private InputAwareLayout container; - private View composePanel; - protected Stub reminderView; - private Stub unverifiedBannerView; + private MasterSecret masterSecret; + protected ComposeText composeText; + private AnimatingToggle buttonToggle; + private SendButton sendButton; + private ImageButton attachButton; + protected ConversationTitleView titleView; + private TextView charactersLeft; + private ConversationFragment fragment; + private Button unblockButton; + private Button makeDefaultSmsButton; + private InputAwareLayout container; + private View composePanel; + protected Stub reminderView; + private Stub unverifiedBannerView; + private Stub groupShareProfileView; private AttachmentTypeSelector attachmentTypeSelector; private AttachmentManager attachmentManager; @@ -323,6 +325,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity titleView.setTitle(recipient); setActionBarColor(recipient.getColor()); setBlockedUserState(recipient, isSecureText, isDefaultSms); + setGroupShareProfileReminder(recipient); calculateCharactersRemaining(); MessageNotifier.setVisibleThread(threadId); @@ -1149,6 +1152,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity container = ViewUtil.findById(this, R.id.layout_container); reminderView = ViewUtil.findStubById(this, R.id.reminder_stub); unverifiedBannerView = ViewUtil.findStubById(this, R.id.unverified_banner_stub); + groupShareProfileView = ViewUtil.findStubById(this, R.id.group_share_profile_view_stub); quickAttachmentDrawer = ViewUtil.findById(this, R.id.quick_attachment_drawer); quickAttachmentToggle = ViewUtil.findById(this, R.id.quick_attachment_toggle); inputPanel = ViewUtil.findById(this, R.id.bottom_panel); @@ -1274,6 +1278,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity titleView.setVerified(identityRecords.isVerified()); setBlockedUserState(recipient, isSecureText, isDefaultSms); setActionBarColor(recipient.getColor()); + setGroupShareProfileReminder(recipient); updateInviteReminder(recipient.hasSeenInviteReminder()); updateDefaultSubscriptionId(recipient.getDefaultSubscriptionId()); initializeSecurity(isSecureText, isDefaultSms); @@ -1460,6 +1465,15 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } } + private void setGroupShareProfileReminder(@NonNull Recipient recipient) { + if (recipient.isPushGroupRecipient() && !recipient.isProfileSharing()) { + groupShareProfileView.get().setRecipient(recipient); + groupShareProfileView.get().setVisibility(View.VISIBLE); + } else if (groupShareProfileView.resolved()) { + groupShareProfileView.get().setVisibility(View.GONE); + } + } + private void calculateCharactersRemaining() { String messageBody = composeText.getTextTrimmed(); TransportOption transportOption = sendButton.getSelectedTransport(); diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java index 639666bd3b..713b900cf8 100644 --- a/src/org/thoughtcrime/securesms/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/ConversationFragment.java @@ -56,6 +56,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsSmsDatabase; +import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.loaders.ConversationLoader; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord; @@ -430,7 +431,7 @@ public class ConversationFragment extends Fragment setLastSeen(loader.getLastSeen()); } - if (!loader.hasSent() && !recipient.isGroupRecipient() && recipient.getName() == null) { + if (!loader.hasSent() && !recipient.isSystemContact() && !recipient.isGroupRecipient() && recipient.getRegistered() != RecipientDatabase.RegisteredState.REGISTERED) { getListAdapter().setHeaderView(unknownSenderView); } else { getListAdapter().setHeaderView(null); diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index 363b67887b..7da7d0c521 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -71,9 +71,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit initializeContactUpdatesReceiver(); - Intent intent = new Intent(this, CreateProfileActivity.class); - startActivity(intent); - RatingManager.showRatingDialogIfNecessary(this); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java index 5bffd19617..8735a7fdc6 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java @@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.requirements.NetworkRequirement; +import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; @@ -138,6 +139,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { { SignalServiceMessageSender messageSender = messageSenderFactory.create(); String groupId = message.getRecipient().getAddress().toGroupString(); + Optional profileKey = getProfileKey(message.getRecipient()); List recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, false); MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints(); List scaledAttachments = scaleAttachments(masterSecret, mediaConstraints, message.getAttachments()); @@ -154,15 +156,23 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { SignalServiceAttachment avatar = attachmentStreams.isEmpty() ? null : attachmentStreams.get(0); SignalServiceGroup.Type type = groupMessage.isGroupQuit() ? SignalServiceGroup.Type.QUIT : SignalServiceGroup.Type.UPDATE; SignalServiceGroup group = new SignalServiceGroup(type, GroupUtil.getDecodedId(groupId), groupContext.getName(), groupContext.getMembersList(), avatar); - SignalServiceDataMessage groupDataMessage = new SignalServiceDataMessage(message.getSentTimeMillis(), group, null, null); + SignalServiceDataMessage groupDataMessage = SignalServiceDataMessage.newBuilder() + .withTimestamp(message.getSentTimeMillis()) + .asGroupMessage(group) + .build(); messageSender.sendMessage(addresses, groupDataMessage); } else { SignalServiceGroup group = new SignalServiceGroup(GroupUtil.getDecodedId(groupId)); - SignalServiceDataMessage groupMessage = new SignalServiceDataMessage(message.getSentTimeMillis(), group, - attachmentStreams, message.getBody(), false, - (int)(message.getExpiresIn() / 1000), - message.isExpirationUpdate(), null); + SignalServiceDataMessage groupMessage = SignalServiceDataMessage.newBuilder() + .withTimestamp(message.getSentTimeMillis()) + .asGroupMessage(group) + .withAttachments(attachmentStreams) + .withBody(message.getBody()) + .withExpiration((int)(message.getExpiresIn() / 1000)) + .asExpirationUpdate(message.isExpirationUpdate()) + .withProfileKey(profileKey.orNull()) + .build(); messageSender.sendMessage(addresses, groupMessage); } diff --git a/src/org/thoughtcrime/securesms/profiles/GroupShareProfileView.java b/src/org/thoughtcrime/securesms/profiles/GroupShareProfileView.java new file mode 100644 index 0000000000..47bd3db448 --- /dev/null +++ b/src/org/thoughtcrime/securesms/profiles/GroupShareProfileView.java @@ -0,0 +1,69 @@ +package org.thoughtcrime.securesms.profiles; + + +import android.content.Context; +import android.os.Build; +import android.support.annotation.AttrRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; +import android.support.annotation.StyleRes; +import android.support.v7.app.AlertDialog; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.util.ViewUtil; + +public class GroupShareProfileView extends FrameLayout { + + private View container; + private @Nullable Recipient recipient; + + public GroupShareProfileView(@NonNull Context context) { + super(context); + initialize(); + } + + public GroupShareProfileView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initialize(); + } + + public GroupShareProfileView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { + super(context, attrs, defStyleAttr); + initialize(); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public GroupShareProfileView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initialize(); + } + + private void initialize() { + inflate(getContext(), R.layout.profile_group_share_view, this); + + this.container = ViewUtil.findById(this, R.id.container); + this.container.setOnClickListener(view -> { + if (this.recipient != null) { + new AlertDialog.Builder(getContext()) + .setIconAttribute(R.attr.dialog_info_icon) + .setTitle(R.string.GroupShareProfileView_share_your_profile_name_and_photo_with_this_group) + .setMessage(R.string.GroupShareProfileView_do_you_want_to_make_your_profile_name_and_photo_visible_to_all_current_and_future_members_of_this_group) + .setPositiveButton(R.string.GroupShareProfileView_make_visible, (dialog, which) -> { + DatabaseFactory.getRecipientDatabase(getContext()).setProfileSharing(recipient, true); + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + }); + } + + public void setRecipient(@NonNull Recipient recipient) { + this.recipient = recipient; + } +} diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java index 8b4756cfc9..52332266dc 100644 --- a/src/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java @@ -430,6 +430,9 @@ public class Recipient implements RecipientModifiedListener { } public synchronized RegisteredState getRegistered() { + if (isPushGroupRecipient()) return RegisteredState.REGISTERED; + else if (isMmsGroupRecipient()) return RegisteredState.NOT_REGISTERED; + return registered; }