Add the ability to block groups.

This also fixes the situation where we block group-leave messages,
preventing blocked contacts from leaving groups.

Fixes #7970

Also, this forced us to upgrade libsignal-service, which fixes the
websocket timeout issues. Thanks to @dpapavas!

Fixes #6644
This commit is contained in:
Greyson Parrelli 2018-09-10 08:40:00 -07:00
parent 741b775d3e
commit 45c4eafbd7
10 changed files with 211 additions and 76 deletions

View File

@ -77,7 +77,7 @@ dependencies {
compile 'com.google.android.exoplayer:exoplayer-core:2.8.4' compile 'com.google.android.exoplayer:exoplayer-core:2.8.4'
compile 'com.google.android.exoplayer:exoplayer-ui:2.8.4' compile 'com.google.android.exoplayer:exoplayer-ui:2.8.4'
compile 'org.whispersystems:signal-service-android:2.7.6' compile 'org.whispersystems:signal-service-android:2.8.1'
compile 'org.whispersystems:webrtc-android:M69' compile 'org.whispersystems:webrtc-android:M69'
compile "me.leolin:ShortcutBadger:1.1.16" compile "me.leolin:ShortcutBadger:1.1.16"
@ -171,7 +171,7 @@ dependencyVerification {
'com.google.android.gms:play-services-maps:45e8021e7ddac4a44a82a0e9698991389ded3023d35c58f38dbd86d54211ec0e', 'com.google.android.gms:play-services-maps:45e8021e7ddac4a44a82a0e9698991389ded3023d35c58f38dbd86d54211ec0e',
'com.google.android.exoplayer:exoplayer-ui:027557b2d69b15e1852a2530b36971f0dcc177abae240ee35e05f63502cdb0a7', 'com.google.android.exoplayer:exoplayer-ui:027557b2d69b15e1852a2530b36971f0dcc177abae240ee35e05f63502cdb0a7',
'com.google.android.exoplayer:exoplayer-core:e69b409e11887c955deb373357c30eeabf183395db0092b4817e0f80bb467d5b', 'com.google.android.exoplayer:exoplayer-core:e69b409e11887c955deb373357c30eeabf183395db0092b4817e0f80bb467d5b',
'org.whispersystems:signal-service-android:823eed29e64fb0aa30d2078cb5ec0245e2a0713a4028121329c5c28788ef27f8', 'org.whispersystems:signal-service-android:414e91598abd941eb3be9a85702538cc9928d8c22f00e07716b83a096cbbe54d',
'org.whispersystems:webrtc-android:5493c92141ce884fc5ce8240d783232f4fe14bd17a8d0d7d1bd4944d0bd1682f', 'org.whispersystems:webrtc-android:5493c92141ce884fc5ce8240d783232f4fe14bd17a8d0d7d1bd4944d0bd1682f',
'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774', 'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774',
'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb', 'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
@ -218,7 +218,7 @@ dependencyVerification {
'com.github.bumptech.glide:gifdecoder:59ccf3bb0cec11dab4b857382cbe0b171111b6fc62bf141adce4e1180889af15', 'com.github.bumptech.glide:gifdecoder:59ccf3bb0cec11dab4b857382cbe0b171111b6fc62bf141adce4e1180889af15',
'com.android.support:support-annotations:af05330d997eb92a066534dbe0a3ea24347d26d7001221092113ae02a8f233da', 'com.android.support:support-annotations:af05330d997eb92a066534dbe0a3ea24347d26d7001221092113ae02a8f233da',
'org.whispersystems:signal-protocol-android:5b8acded7f2a40178eb90ab8e8cbfec89d170d91b3ff5e78487d1098df6185a1', 'org.whispersystems:signal-protocol-android:5b8acded7f2a40178eb90ab8e8cbfec89d170d91b3ff5e78487d1098df6185a1',
'org.whispersystems:signal-service-java:6169643c65dcba8c784744006fc3afd9b6f309041b310a33a624121e3577433a', 'org.whispersystems:signal-service-java:c7ab92374e9656ba86a8d859cec71d03a68bba3e7ec0b7c597b726bf720eac21',
'com.github.bumptech.glide:disklrucache:c1b1b6f5bbd01e2fcdc9d7f60913c8d338bdb65ed4a93bfa02b56f19daaade4b', 'com.github.bumptech.glide:disklrucache:c1b1b6f5bbd01e2fcdc9d7f60913c8d338bdb65ed4a93bfa02b56f19daaade4b',
'com.github.bumptech.glide:annotations:bede99ef9f71517a4274bac18fd3e483e9f2b6108d7d6fe8f4949be4aa4d9512', 'com.github.bumptech.glide:annotations:bede99ef9f71517a4274bac18fd3e483e9f2b6108d7d6fe8f4949be4aa4d9512',
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a', 'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',

View File

@ -148,7 +148,9 @@
<string name="ConversationActivity_error_leaving_group">Error leaving group</string> <string name="ConversationActivity_error_leaving_group">Error leaving group</string>
<string name="ConversationActivity_specify_recipient">Please choose a contact</string> <string name="ConversationActivity_specify_recipient">Please choose a contact</string>
<string name="ConversationActivity_unblock_this_contact_question">Unblock this contact?</string> <string name="ConversationActivity_unblock_this_contact_question">Unblock this contact?</string>
<string name="ConversationActivity_unblock_this_group_question">Unblock this group?</string>
<string name="ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact">You will once again be able to receive messages and calls from this contact.</string> <string name="ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact">You will once again be able to receive messages and calls from this contact.</string>
<string name="ConversationActivity_unblock_this_group_description">Existing members will be able to add you to the group again.</string>
<string name="ConversationActivity_unblock">Unblock</string> <string name="ConversationActivity_unblock">Unblock</string>
<string name="ConversationActivity_attachment_exceeds_size_limits">Attachment exceeds size limits for the type of message you\'re sending.</string> <string name="ConversationActivity_attachment_exceeds_size_limits">Attachment exceeds size limits for the type of message you\'re sending.</string>
<string name="ConversationActivity_quick_camera_unavailable">Camera unavailable</string> <string name="ConversationActivity_quick_camera_unavailable">Camera unavailable</string>
@ -479,9 +481,15 @@
<!-- RecipientPreferencesActivity --> <!-- RecipientPreferencesActivity -->
<string name="RecipientPreferenceActivity_block_this_contact_question">Block this contact?</string> <string name="RecipientPreferenceActivity_block_this_contact_question">Block this contact?</string>
<string name="RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact">You will no longer receive messages and calls from this contact.</string> <string name="RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact">You will no longer receive messages and calls from this contact.</string>
<string name="RecipientPreferenceActivity_block_and_leave_group">Block and leave this group?</string>
<string name="RecipientPreferenceActivity_block_group">Block this group?</string>
<string name="RecipientPreferenceActivity_block_and_leave_group_description">You will no longer receive messages or updates from this group.</string>
<string name="RecipientPreferenceActivity_block">Block</string> <string name="RecipientPreferenceActivity_block">Block</string>
<string name="RecipientPreferenceActivity_unblock_this_contact_question">Unblock this contact?</string> <string name="RecipientPreferenceActivity_unblock_this_contact_question">Unblock this contact?</string>
<string name="RecipientPreferenceActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact">You will once again be able to receive messages and calls from this contact.</string> <string name="RecipientPreferenceActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact">You will once again be able to receive messages and calls from this contact.</string>
<string name="RecipientPreferenceActivity_unblock_this_group_question">Unblock this group?</string>
<string name="RecipientPreferenceActivity_unblock_this_group_description">Existing members will be able to add you to the group again.</string>
<string name="RecipientPreferenceActivity_error_leaving_group">Error leaving group</string>
<string name="RecipientPreferenceActivity_unblock">Unblock</string> <string name="RecipientPreferenceActivity_unblock">Unblock</string>
<string name="RecipientPreferenceActivity_enabled">Enabled</string> <string name="RecipientPreferenceActivity_enabled">Enabled</string>
<string name="RecipientPreferenceActivity_disabled">Disabled</string> <string name="RecipientPreferenceActivity_disabled">Disabled</string>

View File

@ -51,6 +51,8 @@ import android.support.v7.app.AlertDialog;
import android.text.Editable; import android.text.Editable;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import android.util.Pair; import android.util.Pair;
import android.view.KeyEvent; import android.view.KeyEvent;
@ -70,7 +72,6 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.gms.location.places.ui.PlacePicker; import com.google.android.gms.location.places.ui.PlacePicker;
import com.google.protobuf.ByteString;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.Subscribe;
@ -192,7 +193,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import static org.thoughtcrime.securesms.TransportOption.Type; import static org.thoughtcrime.securesms.TransportOption.Type;
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK; import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
/** /**
* Activity for displaying a message thread, as well as * Activity for displaying a message thread, as well as
@ -693,26 +693,34 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
private void handleUnblock() { private void handleUnblock() {
int titleRes = R.string.ConversationActivity_unblock_this_contact_question;
int bodyRes = R.string.ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact;
if (recipient.isGroupRecipient()) {
titleRes = R.string.ConversationActivity_unblock_this_group_question;
bodyRes = R.string.ConversationActivity_unblock_this_group_description;
}
//noinspection CodeBlock2Expr //noinspection CodeBlock2Expr
new AlertDialog.Builder(this) new AlertDialog.Builder(this)
.setTitle(R.string.ConversationActivity_unblock_this_contact_question) .setTitle(titleRes)
.setMessage(R.string.ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact) .setMessage(bodyRes)
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.ConversationActivity_unblock, (dialog, which) -> { .setPositiveButton(R.string.ConversationActivity_unblock, (dialog, which) -> {
new AsyncTask<Void, Void, Void>() { new AsyncTask<Void, Void, Void>() {
@Override @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientDatabase(ConversationActivity.this) DatabaseFactory.getRecipientDatabase(ConversationActivity.this)
.setBlocked(recipient, false); .setBlocked(recipient, false);
ApplicationContext.getInstance(ConversationActivity.this) ApplicationContext.getInstance(ConversationActivity.this)
.getJobManager() .getJobManager()
.add(new MultiDeviceBlockedUpdateJob(ConversationActivity.this)); .add(new MultiDeviceBlockedUpdateJob(ConversationActivity.this));
return null; return null;
} }
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}).show(); }).show();
} }
@TargetApi(Build.VERSION_CODES.KITKAT) @TargetApi(Build.VERSION_CODES.KITKAT)
@ -847,24 +855,21 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
builder.setCancelable(true); builder.setCancelable(true);
builder.setMessage(getString(R.string.ConversationActivity_are_you_sure_you_want_to_leave_this_group)); builder.setMessage(getString(R.string.ConversationActivity_are_you_sure_you_want_to_leave_this_group));
builder.setPositiveButton(R.string.yes, (dialog, which) -> { builder.setPositiveButton(R.string.yes, (dialog, which) -> {
Context self = ConversationActivity.this; Recipient groupRecipient = getRecipient();
long threadId = DatabaseFactory.getThreadDatabase(this).getThreadIdFor(groupRecipient);
Optional<OutgoingGroupMediaMessage> leaveMessage = GroupUtil.createGroupLeaveMessage(this, groupRecipient);
try { if (threadId != -1 && leaveMessage.isPresent()) {
String groupId = getRecipient().getAddress().toGroupString(); MessageSender.send(this, leaveMessage.get(), threadId, false, null);
DatabaseFactory.getGroupDatabase(self).setActive(groupId, false);
GroupContext context = GroupContext.newBuilder() GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this);
.setId(ByteString.copyFrom(GroupUtil.getDecodedId(groupId))) String groupId = groupRecipient.getAddress().toGroupString();
.setType(GroupContext.Type.QUIT) groupDatabase.setActive(groupId, false);
.build(); groupDatabase.remove(groupId, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)));
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(getRecipient(), context, null, System.currentTimeMillis(), 0, null, Collections.emptyList());
MessageSender.send(self, outgoingMessage, threadId, false, null);
DatabaseFactory.getGroupDatabase(self).remove(groupId, Address.fromSerialized(TextSecurePreferences.getLocalNumber(self)));
initializeEnabledCheck(); initializeEnabledCheck();
} catch (IOException e) { } else {
Log.w(TAG, e); Toast.makeText(this, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show();
Toast.makeText(self, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show();
} }
}); });

View File

@ -3,7 +3,6 @@ package org.thoughtcrime.securesms;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Color; import android.graphics.Color;
@ -30,6 +29,7 @@ import android.support.v7.widget.Toolbar;
import android.telephony.PhoneNumberUtils; import android.telephony.PhoneNumberUtils;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat; import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import android.util.Pair; import android.util.Pair;
import android.view.MenuItem; import android.view.MenuItem;
@ -37,6 +37,7 @@ import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.DiskCacheStrategy;
@ -55,6 +56,7 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment; import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
@ -62,11 +64,13 @@ import org.thoughtcrime.securesms.preferences.widgets.ColorPickerPreference;
import org.thoughtcrime.securesms.preferences.widgets.ContactPreference; import org.thoughtcrime.securesms.preferences.widgets.ContactPreference;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.Dialogs; import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
@ -378,10 +382,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
if (recipient.isGroupRecipient()) { if (recipient.isGroupRecipient()) {
if (colorPreference != null) colorPreference.setVisible(false); if (colorPreference != null) colorPreference.setVisible(false);
if (blockPreference != null) blockPreference.setVisible(false);
if (identityPreference != null) identityPreference.setVisible(false); if (identityPreference != null) identityPreference.setVisible(false);
if (privacyCategory != null) privacyCategory.setVisible(false);
if (divider != null) divider.setVisible(false);
if (aboutCategory != null) getPreferenceScreen().removePreference(aboutCategory); if (aboutCategory != null) getPreferenceScreen().removePreference(aboutCategory);
if (aboutDivider != null) getPreferenceScreen().removePreference(aboutDivider); if (aboutDivider != null) getPreferenceScreen().removePreference(aboutDivider);
} else { } else {
@ -659,31 +660,55 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
} }
private void handleBlock() { private void handleBlock() {
new AlertDialog.Builder(getActivity()) new AsyncTask<Void, Void, Pair<Integer, Integer>>() {
.setTitle(R.string.RecipientPreferenceActivity_block_this_contact_question)
.setMessage(R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact) @Override
.setCancelable(true) protected Pair<Integer, Integer> doInBackground(Void... voids) {
.setNegativeButton(android.R.string.cancel, null) int titleRes = R.string.RecipientPreferenceActivity_block_this_contact_question;
.setPositiveButton(R.string.RecipientPreferenceActivity_block, new DialogInterface.OnClickListener() { int bodyRes = R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact;
@Override
public void onClick(DialogInterface dialog, int which) { if (recipient.isGroupRecipient()) {
setBlocked(recipient, true); bodyRes = R.string.RecipientPreferenceActivity_block_and_leave_group_description;
if (recipient.isGroupRecipient() && DatabaseFactory.getGroupDatabase(getContext()).isActive(recipient.getAddress().toGroupString())) {
titleRes = R.string.RecipientPreferenceActivity_block_and_leave_group;
} else {
titleRes = R.string.RecipientPreferenceActivity_block_group;
} }
}).show(); }
return new Pair<>(titleRes, bodyRes);
}
@Override
protected void onPostExecute(Pair<Integer, Integer> titleAndBody) {
new AlertDialog.Builder(getActivity())
.setTitle(titleAndBody.first)
.setMessage(titleAndBody.second)
.setCancelable(true)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.RecipientPreferenceActivity_block, (dialog, which) -> {
setBlocked(recipient, true);
}).show();
}
}.execute();
} }
private void handleUnblock() { private void handleUnblock() {
int titleRes = R.string.RecipientPreferenceActivity_unblock_this_contact_question;
int bodyRes = R.string.RecipientPreferenceActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact;
if (recipient.isGroupRecipient()) {
titleRes = R.string.RecipientPreferenceActivity_unblock_this_group_question;
bodyRes = R.string.RecipientPreferenceActivity_unblock_this_group_description;
}
new AlertDialog.Builder(getActivity()) new AlertDialog.Builder(getActivity())
.setTitle(R.string.RecipientPreferenceActivity_unblock_this_contact_question) .setTitle(titleRes)
.setMessage(R.string.RecipientPreferenceActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact) .setMessage(bodyRes)
.setCancelable(true) .setCancelable(true)
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.RecipientPreferenceActivity_unblock, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.RecipientPreferenceActivity_unblock, (dialog, which) -> setBlocked(recipient, false)).show();
@Override
public void onClick(DialogInterface dialog, int which) {
setBlocked(recipient, false);
}
}).show();
} }
private void setBlocked(final Recipient recipient, final boolean blocked) { private void setBlocked(final Recipient recipient, final boolean blocked) {
@ -695,6 +720,23 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
DatabaseFactory.getRecipientDatabase(context) DatabaseFactory.getRecipientDatabase(context)
.setBlocked(recipient, blocked); .setBlocked(recipient, blocked);
if (recipient.isGroupRecipient() && DatabaseFactory.getGroupDatabase(context).isActive(recipient.getAddress().toGroupString())) {
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
Optional<OutgoingGroupMediaMessage> leaveMessage = GroupUtil.createGroupLeaveMessage(context, recipient);
if (threadId != -1 && leaveMessage.isPresent()) {
MessageSender.send(context, leaveMessage.get(), threadId, false, null);
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
String groupId = recipient.getAddress().toGroupString();
groupDatabase.setActive(groupId, false);
groupDatabase.remove(groupId, Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)));
} else {
Log.w(TAG, "Failed to leave group. Can't block.");
Toast.makeText(context, R.string.RecipientPreferenceActivity_error_leaving_group, Toast.LENGTH_LONG).show();
}
}
ApplicationContext.getInstance(context) ApplicationContext.getInstance(context)
.getJobManager() .getJobManager()
.add(new MultiDeviceBlockedUpdateJob(context)); .add(new MultiDeviceBlockedUpdateJob(context));

View File

@ -44,6 +44,9 @@ import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.util.CredentialsProvider; import org.whispersystems.signalservice.api.util.CredentialsProvider;
import org.whispersystems.signalservice.api.util.RealtimeSleepTimer;
import org.whispersystems.signalservice.api.util.SleepTimer;
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
import org.whispersystems.signalservice.api.websocket.ConnectivityListener; import org.whispersystems.signalservice.api.websocket.ConnectivityListener;
import dagger.Module; import dagger.Module;
@ -124,10 +127,13 @@ public class SignalCommunicationModule {
@Provides @Provides
synchronized SignalServiceMessageReceiver provideSignalMessageReceiver() { synchronized SignalServiceMessageReceiver provideSignalMessageReceiver() {
if (this.messageReceiver == null) { if (this.messageReceiver == null) {
SleepTimer sleepTimer = TextSecurePreferences.isGcmDisabled(context) ? new RealtimeSleepTimer(context) : new UptimeSleepTimer();
this.messageReceiver = new SignalServiceMessageReceiver(networkAccess.getConfiguration(context), this.messageReceiver = new SignalServiceMessageReceiver(networkAccess.getConfiguration(context),
new DynamicCredentialsProvider(context), new DynamicCredentialsProvider(context),
BuildConfig.USER_AGENT, BuildConfig.USER_AGENT,
new PipeConnectivityListener()); new PipeConnectivityListener(),
sleepTimer);
} }
return this.messageReceiver; return this.messageReceiver;

View File

@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement; import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage; import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
@ -47,17 +48,20 @@ public class MultiDeviceBlockedUpdateJob extends MasterSecretJob implements Inje
RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context); RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context);
try (RecipientReader reader = database.readerForBlocked(database.getBlocked())) { try (RecipientReader reader = database.readerForBlocked(database.getBlocked())) {
List<String> blocked = new LinkedList<>(); List<String> blockedIndividuals = new LinkedList<>();
List<byte[]> blockedGroups = new LinkedList<>();
Recipient recipient; Recipient recipient;
while ((recipient = reader.getNext()) != null) { while ((recipient = reader.getNext()) != null) {
if (!recipient.isGroupRecipient()) { if (recipient.isGroupRecipient()) {
blocked.add(recipient.getAddress().serialize()); blockedGroups.add(GroupUtil.getDecodedId(recipient.getAddress().toGroupString()));
} else {
blockedIndividuals.add(recipient.getAddress().serialize());
} }
} }
messageSender.sendMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(blocked))); messageSender.sendMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(blockedIndividuals, blockedGroups)));
} }
} }

View File

@ -75,7 +75,9 @@ public class MultiDeviceGroupUpdateJob extends MasterSecretJob implements Inject
out.write(new DeviceGroup(record.getId(), Optional.fromNullable(record.getTitle()), out.write(new DeviceGroup(record.getId(), Optional.fromNullable(record.getTitle()),
members, getAvatar(record.getAvatar()), members, getAvatar(record.getAvatar()),
record.isActive(), expirationTimer)); record.isActive(), expirationTimer,
Optional.of(recipient.getColor().serialize()),
recipient.isBlocked()));
} }
} }

View File

@ -175,6 +175,11 @@ public class PushDecryptJob extends ContextJob {
SignalServiceContent content = cipher.decrypt(envelope); SignalServiceContent content = cipher.decrypt(envelope);
if (shouldIgnore(envelope, content)) {
Log.i(TAG, "Ignoring message.");
return;
}
if (content.getDataMessage().isPresent()) { if (content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get(); SignalServiceDataMessage message = content.getDataMessage().get();
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent(); boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent();
@ -941,4 +946,36 @@ public class PushDecryptJob extends ContextJob {
return Recipient.from(context, Address.fromExternal(context, envelope.getSource()), false); return Recipient.from(context, Address.fromExternal(context, envelope.getSource()), false);
} }
} }
private boolean shouldIgnore(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content) {
Recipient sender = Recipient.from(context, Address.fromExternal(context, envelope.getSource()), false);
if (content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get();
Recipient conversation = getMessageDestination(envelope, message);
if (conversation.isGroupRecipient() && conversation.isBlocked()) {
return true;
} else if (conversation.isGroupRecipient()) {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
Optional<String> groupId = message.getGroupInfo().isPresent() ? Optional.of(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false))
: Optional.absent();
boolean isTextMessage = message.getBody().isPresent();
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent();
boolean isExpireMessage = message.isExpirationUpdate();
boolean isContentMessage = !message.isGroupUpdate() && (isTextMessage || isMediaMessage || isExpireMessage);
boolean isGroupActive = groupId.isPresent() && groupDatabase.isActive(groupId.get());
boolean isLeaveMessage = message.getGroupInfo().isPresent() && message.getGroupInfo().get().getType() == SignalServiceGroup.Type.QUIT;
return (isContentMessage && !isGroupActive) || (sender.isBlocked() && !isLeaveMessage);
} else {
return sender.isBlocked();
}
} else if (content.getCallMessage().isPresent()) {
return sender.isBlocked();
}
return false;
}
} }

View File

@ -34,22 +34,17 @@ public abstract class PushReceivedJob extends ContextJob {
if (envelope.isReceipt()) { if (envelope.isReceipt()) {
handleReceipt(envelope); handleReceipt(envelope);
} else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage()) { } else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage()) {
handleMessage(envelope, source); handleMessage(envelope);
} else { } else {
Log.w(TAG, "Received envelope of unknown type: " + envelope.getType()); Log.w(TAG, "Received envelope of unknown type: " + envelope.getType());
} }
} }
private void handleMessage(SignalServiceEnvelope envelope, Address source) { private void handleMessage(SignalServiceEnvelope envelope) {
Recipient recipients = Recipient.from(context, source, false); long messageId = DatabaseFactory.getPushDatabase(context).insert(envelope);
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); ApplicationContext.getInstance(context)
.getJobManager()
if (!recipients.isBlocked()) { .add(new PushDecryptJob(context, messageId));
long messageId = DatabaseFactory.getPushDatabase(context).insert(envelope);
jobManager.add(new PushDecryptJob(context, messageId));
} else {
Log.w(TAG, "*** Received blocked push message, ignoring...");
}
} }
private void handleReceipt(SignalServiceEnvelope envelope) { private void handleReceipt(SignalServiceEnvelope envelope) {

View File

@ -3,14 +3,23 @@ package org.thoughtcrime.securesms.util;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -20,7 +29,7 @@ public class GroupUtil {
private static final String ENCODED_SIGNAL_GROUP_PREFIX = "__textsecure_group__!"; private static final String ENCODED_SIGNAL_GROUP_PREFIX = "__textsecure_group__!";
private static final String ENCODED_MMS_GROUP_PREFIX = "__signal_mms_group__!"; private static final String ENCODED_MMS_GROUP_PREFIX = "__signal_mms_group__!";
private static final String TAG = GroupUtil.class.getSimpleName(); private static final String TAG = GroupUtil.class.getSimpleName();
public static String getEncodedId(byte[] groupId, boolean mms) { public static String getEncodedId(byte[] groupId, boolean mms) {
return (mms ? ENCODED_MMS_GROUP_PREFIX : ENCODED_SIGNAL_GROUP_PREFIX) + Hex.toStringCondensed(groupId); return (mms ? ENCODED_MMS_GROUP_PREFIX : ENCODED_SIGNAL_GROUP_PREFIX) + Hex.toStringCondensed(groupId);
@ -42,6 +51,33 @@ public class GroupUtil {
return groupId.startsWith(ENCODED_MMS_GROUP_PREFIX); return groupId.startsWith(ENCODED_MMS_GROUP_PREFIX);
} }
@WorkerThread
public static Optional<OutgoingGroupMediaMessage> createGroupLeaveMessage(@NonNull Context context, @NonNull Recipient groupRecipient) {
String encodedGroupId = groupRecipient.getAddress().toGroupString();
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
if (!groupDatabase.isActive(encodedGroupId)) {
Log.w(TAG, "Group has already been left.");
return Optional.absent();
}
ByteString decodedGroupId;
try {
decodedGroupId = ByteString.copyFrom(getDecodedId(encodedGroupId));
} catch (IOException e) {
Log.w(TAG, "Failed to decode group ID.", e);
return Optional.absent();
}
GroupContext groupContext = GroupContext.newBuilder()
.setId(decodedGroupId)
.setType(GroupContext.Type.QUIT)
.build();
return Optional.of(new OutgoingGroupMediaMessage(groupRecipient, groupContext, null, System.currentTimeMillis(), 0, null, Collections.emptyList()));
}
public static @NonNull GroupDescription getDescription(@NonNull Context context, @Nullable String encodedGroup) { public static @NonNull GroupDescription getDescription(@NonNull Context context, @Nullable String encodedGroup) {
if (encodedGroup == null) { if (encodedGroup == null) {
return new GroupDescription(context, null); return new GroupDescription(context, null);