Insert resent messages at the proper location.

This commit is contained in:
Greyson Parrelli
2021-06-29 15:13:31 -04:00
committed by Alex Hart
parent 90a27d2227
commit a1c8573fad
20 changed files with 306 additions and 168 deletions

View File

@@ -244,6 +244,15 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
}
)
switchPref(
title = DSLSettingsText.from(R.string.preferences__internal_delay_resends),
summary = DSLSettingsText.from(R.string.preferences__internal_delay_resending_messages_in_response_to_retry_receipts),
isChecked = state.delayResends,
onClick = {
viewModel.setDelayResends(!state.delayResends)
}
)
dividerPref()
sectionHeaderPref(R.string.preferences__internal_calling)

View File

@@ -15,4 +15,5 @@ data class InternalSettingsState(
val useBuiltInEmojiSet: Boolean,
val emojiVersion: EmojiFiles.Version?,
val removeSenderKeyMinimium: Boolean,
val delayResends: Boolean,
)

View File

@@ -70,6 +70,11 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito
refresh()
}
fun setDelayResends(enabled: Boolean) {
preferenceDataStore.putBoolean(InternalValues.DELAY_RESENDS, enabled)
refresh()
}
fun setInternalGroupCallingServer(server: String?) {
preferenceDataStore.putString(InternalValues.CALLING_SERVER, server)
refresh()
@@ -91,7 +96,8 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito
callingServer = SignalStore.internalValues().groupCallingServer(),
useBuiltInEmojiSet = SignalStore.internalValues().forceBuiltInEmoji(),
emojiVersion = null,
removeSenderKeyMinimium = SignalStore.internalValues().removeSenderKeyMinimum()
removeSenderKeyMinimium = SignalStore.internalValues().removeSenderKeyMinimum(),
delayResends = SignalStore.internalValues().delayResends()
)
class Factory(private val repository: InternalSettingsRepository) : ViewModelProvider.Factory {

View File

@@ -1342,7 +1342,7 @@ public class MmsDatabase extends MessageDatabase {
contentValues.put(THREAD_ID, threadId);
contentValues.put(CONTENT_LOCATION, contentLocation);
contentValues.put(STATUS, Status.DOWNLOAD_INITIALIZED);
contentValues.put(DATE_RECEIVED, retrieved.isPushMessage() ? System.currentTimeMillis() : generatePduCompatTimestamp());
contentValues.put(DATE_RECEIVED, retrieved.isPushMessage() ? retrieved.getReceivedTimeMillis() : generatePduCompatTimestamp(retrieved.getReceivedTimeMillis()));
contentValues.put(PART_COUNT, retrieved.getAttachments().size());
contentValues.put(SUBSCRIPTION_ID, retrieved.getSubscriptionId());
contentValues.put(EXPIRES_IN, retrieved.getExpiresIn());
@@ -1448,7 +1448,7 @@ public class MmsDatabase extends MessageDatabase {
contentValues.put(MESSAGE_BOX, Types.BASE_INBOX_TYPE);
contentValues.put(THREAD_ID, threadId);
contentValues.put(STATUS, Status.DOWNLOAD_INITIALIZED);
contentValues.put(DATE_RECEIVED, generatePduCompatTimestamp());
contentValues.put(DATE_RECEIVED, generatePduCompatTimestamp(System.currentTimeMillis()));
contentValues.put(READ, Util.isDefaultSmsProvider(context) ? 0 : 1);
contentValues.put(SUBSCRIPTION_ID, subscriptionId);
@@ -2174,8 +2174,7 @@ public class MmsDatabase extends MessageDatabase {
}
}
private long generatePduCompatTimestamp() {
final long time = System.currentTimeMillis();
private long generatePduCompatTimestamp(long time) {
return time - (time % 1000);
}
}

View File

@@ -1115,7 +1115,7 @@ public class SmsDatabase extends MessageDatabase {
ContentValues values = new ContentValues();
values.put(RECIPIENT_ID, message.getSender().serialize());
values.put(ADDRESS_DEVICE_ID, message.getSenderDeviceId());
values.put(DATE_RECEIVED, System.currentTimeMillis());
values.put(DATE_RECEIVED, message.getReceivedTimestampMillis());
values.put(DATE_SENT, message.getSentTimestampMillis());
values.put(DATE_SERVER, message.getServerTimestampMillis());
values.put(PROTOCOL, message.getProtocol());

View File

@@ -248,7 +248,7 @@ public final class GroupV1MessageProcessor {
} else {
MessageDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
String body = Base64.encodeBytes(storage.toByteArray());
IncomingTextMessage incoming = new IncomingTextMessage(Recipient.externalHighTrustPush(context, content.getSender()).getId(), content.getSenderDevice(), content.getTimestamp(), content.getServerReceivedTimestamp(), body, Optional.of(GroupId.v1orThrow(group.getGroupId())), 0, content.isNeedsReceipt(), content.getServerUuid());
IncomingTextMessage incoming = new IncomingTextMessage(Recipient.externalHighTrustPush(context, content.getSender()).getId(), content.getSenderDevice(), content.getTimestamp(), content.getServerReceivedTimestamp(), System.currentTimeMillis(), body, Optional.of(GroupId.v1orThrow(group.getGroupId())), 0, content.isNeedsReceipt(), content.getServerUuid());
IncomingGroupUpdateMessage groupMessage = new IncomingGroupUpdateMessage(incoming, storage, body);
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(groupMessage);

View File

@@ -521,7 +521,7 @@ public final class GroupsV2StateProcessor {
} else {
MessageDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
RecipientId sender = RecipientId.from(editor.get(), null);
IncomingTextMessage incoming = new IncomingTextMessage(sender, -1, timestamp, timestamp, "", Optional.of(groupId), 0, false, null);
IncomingTextMessage incoming = new IncomingTextMessage(sender, -1, timestamp, timestamp, timestamp, "", Optional.of(groupId), 0, false, null);
IncomingGroupUpdateMessage groupMessage = new IncomingGroupUpdateMessage(incoming, decryptedGroupV2Context);
if (!smsDatabase.insertMessageInbox(groupMessage).isPresent()) {

View File

@@ -248,7 +248,7 @@ public class MmsDownloadJob extends BaseJob {
group = Optional.of(DatabaseFactory.getGroupDatabase(context).getOrCreateMmsGroupForMembers(recipients));
}
IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, retrieved.getDate() * 1000L, -1, attachments, subscriptionId, 0, false, false, false, Optional.of(sharedContacts));
IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, retrieved.getDate() * 1000L, -1, System.currentTimeMillis(), attachments, subscriptionId, 0, false, false, false, Optional.of(sharedContacts));
Optional<InsertResult> insertResult = database.insertMessageInbox(message, contentLocation, threadId);
if (insertResult.isPresent()) {

View File

@@ -6,26 +6,34 @@ import androidx.annotation.Nullable;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.crypto.storage.SignalSenderKeyStore;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.protocol.SenderKeyDistributionMessage;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.ContentHint;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* Resends a previously-sent message in response to receiving a retry receipt.
@@ -36,6 +44,8 @@ public class ResendMessageJob extends BaseJob {
public static final String KEY = "ResendMessageJob";
private static final String TAG = Log.tag(ResendMessageJob.class);
private final RecipientId recipientId;
private final long sentTimestamp;
private final Content content;
@@ -50,7 +60,6 @@ public class ResendMessageJob extends BaseJob {
private static final String KEY_GROUP_ID = "group_id";
private static final String KEY_DISTRIBUTION_ID = "distribution_id";
// TODO [greyson] Maybe just pass in the MessageLogEntry?
public ResendMessageJob(@NonNull RecipientId recipientId,
long sentTimestamp,
@NonNull Content content,
@@ -108,6 +117,11 @@ public class ResendMessageJob extends BaseJob {
@Override
protected void onRun() throws Exception {
if (SignalStore.internalValues().delayResends()) {
Log.w(TAG, "Delaying resend by 10 sec because of an internal preference.");
ThreadUtil.sleep(10000);
}
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
Recipient recipient = Recipient.resolved(recipientId);
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
@@ -121,7 +135,17 @@ public class ResendMessageJob extends BaseJob {
contentToSend = contentToSend.toBuilder().setSenderKeyDistributionMessage(distributionBytes).build();
}
messageSender.resendContent(address, access, sentTimestamp, contentToSend, contentHint, Optional.fromNullable(groupId).transform(GroupId::getDecodedId));
SendMessageResult result = messageSender.resendContent(address, access, sentTimestamp, contentToSend, contentHint, Optional.fromNullable(groupId).transform(GroupId::getDecodedId));
if (result.isSuccess() && distributionId != null) {
List<SignalProtocolAddress> addresses = result.getSuccess()
.getDevices()
.stream()
.map(device -> new SignalProtocolAddress(recipient.requireServiceId(), device))
.collect(Collectors.toList());
new SignalSenderKeyStore(context).markSenderKeySharedWith(distributionId, addresses);
}
}
@Override

View File

@@ -4,6 +4,7 @@ import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.crypto.storage.SignalSenderKeyStore;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
@@ -14,16 +15,19 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.protocol.SenderKeyDistributionMessage;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* Sends a {@link SenderKeyDistributionMessage} to a target recipient.
@@ -92,7 +96,17 @@ public final class SenderKeyDistributionSendJob extends BaseJob {
SenderKeyDistributionMessage message = messageSender.getOrCreateNewGroupSession(distributionId);
List<Optional<UnidentifiedAccessPair>> access = UnidentifiedAccessUtil.getAccessFor(context, Collections.singletonList(recipient));
messageSender.sendSenderKeyDistributionMessage(address, access, message, groupId.getDecodedId());
SendMessageResult result = messageSender.sendSenderKeyDistributionMessage(address, access, message, groupId.getDecodedId()).get(0);
if (result.isSuccess()) {
List<SignalProtocolAddress> addresses = result.getSuccess()
.getDevices()
.stream()
.map(device -> new SignalProtocolAddress(recipient.requireServiceId(), device))
.collect(Collectors.toList());
new SignalSenderKeyStore(context).markSenderKeySharedWith(distributionId, addresses);
}
}
@Override

View File

@@ -21,6 +21,7 @@ public final class InternalValues extends SignalStoreValues {
public static final String FORCE_CENSORSHIP = "internal.force_censorship";
public static final String FORCE_BUILT_IN_EMOJI = "internal.force_built_in_emoji";
public static final String REMOVE_SENDER_KEY_MINIMUM = "internal.remove_sender_key_minimum";
public static final String DELAY_RESENDS = "internal.delay_resends";
public static final String CALLING_SERVER = "internal.calling_server";
InternalValues(KeyValueStore store) {
@@ -99,6 +100,13 @@ public final class InternalValues extends SignalStoreValues {
return FeatureFlags.internalUser() && getBoolean(REMOVE_SENDER_KEY_MINIMUM, false);
}
/**
* Delay resending messages in response to retry receipts by 10 seconds.
*/
public synchronized boolean delayResends() {
return FeatureFlags.internalUser() && getBoolean(DELAY_RESENDS, false);
}
/**
* Disable initiating a GV1->GV2 auto-migration. You can still recognize a group has been
* auto-migrated.

View File

@@ -188,6 +188,10 @@ public final class GroupSendUtil {
int successCount = (int) results.stream().filter(SendMessageResult::isSuccess).count();
Log.d(TAG, "Successfully sent using sender key to " + successCount + "/" + targets.size() + " sender key targets.");
if (sendOperation.shouldIncludeInMessageLog()) {
DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(sendOperation.getSentTimestamp(), senderKeyTargets, results, sendOperation.getContentHint(), sendOperation.getRelatedMessageId(), sendOperation.isRelatedMessageMms());
}
} catch (NoSessionException e) {
Log.w(TAG, "No session. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
@@ -198,9 +202,6 @@ public final class GroupSendUtil {
}
if (cancelationSignal != null && cancelationSignal.isCanceled()) {
if (sendOperation.shouldIncludeInMessageLog()) {
DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(sendOperation.getSentTimestamp(), allTargets, allResults, sendOperation.getContentHint(), sendOperation.getRelatedMessageId(), sendOperation.isRelatedMessageMms());
}
throw new CancelationException();
}
@@ -217,10 +218,10 @@ public final class GroupSendUtil {
int successCount = (int) results.stream().filter(SendMessageResult::isSuccess).count();
Log.d(TAG, "Successfully using 1:1 to " + successCount + "/" + targets.size() + " legacy targets.");
}
if (sendOperation.shouldIncludeInMessageLog()) {
DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(sendOperation.getSentTimestamp(), allTargets, allResults, sendOperation.getContentHint(), sendOperation.getRelatedMessageId(), sendOperation.isRelatedMessageMms());
if (sendOperation.shouldIncludeInMessageLog()) {
DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(sendOperation.getSentTimestamp(), legacyTargets, results, sendOperation.getContentHint(), sendOperation.getRelatedMessageId(), sendOperation.isRelatedMessageMms());
}
}
return allResults;

View File

@@ -159,8 +159,8 @@ public class IncomingMessageProcessor {
}
private void processReceipt(@NonNull SignalServiceEnvelope envelope) {
Log.i(TAG, "Received server receipt for " + envelope.getTimestamp());
Recipient sender = Recipient.externalHighTrustPush(context, envelope.getSourceAddress());
Log.i(TAG, "Received server receipt. Sender: " + sender.getId() + ", Device: " + envelope.getSourceDevice() + ", Timestamp: " + envelope.getTimestamp());
mmsSmsDatabase.incrementDeliveryReceiptCount(new SyncMessageId(sender.getId(), envelope.getTimestamp()), System.currentTimeMillis());
DatabaseFactory.getMessageLogDatabase(context).deleteEntryForRecipient(envelope.getTimestamp(), sender.getId(), envelope.getSourceDevice());

View File

@@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.database.model.Mention;
import org.thoughtcrime.securesms.database.model.MessageLogEntry;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.PendingRetryReceiptModel;
import org.thoughtcrime.securesms.database.model.ReactionRecord;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
@@ -229,6 +230,10 @@ public final class MessageContentProcessor {
return;
}
RecipientId senderId = RecipientId.fromHighTrust(content.getSender());
PendingRetryReceiptModel pending = DatabaseFactory.getPendingRetryReceiptDatabase(context).get(senderId, content.getTimestamp());
long receivedTime = handlePendingRetry(pending, content);
log(String.valueOf(content.getTimestamp()), "Beginning message processing.");
if (content.getSenderKeyDistributionMessage().isPresent()) {
@@ -249,13 +254,13 @@ public final class MessageContentProcessor {
if (isInvalidMessage(message)) handleInvalidMessage(content.getSender(), content.getSenderDevice(), groupId, content.getTimestamp(), smsMessageId);
else if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId);
else if (message.isGroupV1Update()) handleGroupV1Message(content, message, smsMessageId, groupId.get().requireV1());
else if (message.isExpirationUpdate()) handleExpirationUpdate(content, message, smsMessageId, groupId);
else if (message.isGroupV1Update()) handleGroupV1Message(content, message, smsMessageId, groupId.get().requireV1(), receivedTime);
else if (message.isExpirationUpdate()) handleExpirationUpdate(content, message, smsMessageId, groupId, receivedTime);
else if (message.getReaction().isPresent()) handleReaction(content, message);
else if (message.getRemoteDelete().isPresent()) handleRemoteDelete(content, message);
else if (message.getPayment().isPresent()) handlePayment(content, message);
else if (isMediaMessage) handleMediaMessage(content, message, smsMessageId);
else if (message.getBody().isPresent()) handleTextMessage(content, message, smsMessageId, groupId);
else if (isMediaMessage) handleMediaMessage(content, message, smsMessageId, receivedTime);
else if (message.getBody().isPresent()) handleTextMessage(content, message, smsMessageId, groupId, receivedTime);
else if (Build.VERSION.SDK_INT > 19 && message.getGroupCallUpdate().isPresent()) handleGroupCallUpdateMessage(content, message, groupId);
if (groupId.isPresent() && groupDatabase.isUnknownGroup(groupId.get())) {
@@ -332,11 +337,18 @@ public final class MessageContentProcessor {
handleTypingMessage(content, content.getTypingMessage().get());
} else if (content.getDecryptionErrorMessage().isPresent()) {
handleRetryReceipt(content, content.getDecryptionErrorMessage().get());
} else if (content.getSenderKeyDistributionMessage().isPresent()) {
// Already handled, here in order to prevent unrecognized message log
} else {
warn(String.valueOf(content.getTimestamp()), "Got unrecognized message!");
}
resetRecipientToPush(Recipient.externalPush(context, content.getSender()));
if (pending != null) {
warn(content.getTimestamp(), "Pending retry was processed. Deleting.");
DatabaseFactory.getPendingRetryReceiptDatabase(context).delete(pending.getId());
}
} catch (StorageFailedException e) {
warn(String.valueOf(content.getTimestamp()), e);
handleCorruptMessage(e.getSender(), e.getSenderDevice(), timestamp, smsMessageId);
@@ -345,6 +357,33 @@ public final class MessageContentProcessor {
}
}
private long handlePendingRetry(PendingRetryReceiptModel pending, SignalServiceContent content) throws BadGroupIdException {
long receivedTime = System.currentTimeMillis();
if (pending != null) {
warn(content.getTimestamp(), "Incoming message matches a pending retry we were expecting.");
Recipient destination = getMessageDestination(content);
Long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(destination.getId());
if (threadId != null) {
ThreadDatabase.ConversationMetadata metadata = DatabaseFactory.getThreadDatabase(context).getConversationMetadata(threadId);
long visibleThread = ApplicationDependencies.getMessageNotifier().getVisibleThread();
if (threadId != visibleThread && metadata.getLastSeen() > 0 && metadata.getLastSeen() < pending.getReceivedTimestamp()) {
receivedTime = pending.getReceivedTimestamp();
warn(content.getTimestamp(), "Thread has not been opened yet. Using received timestamp of " + receivedTime);
} else {
warn(content.getTimestamp(), "Thread was opened after receiving the original message. Using the current time for received time. (Last seen: " + metadata.getLastSeen() + ", ThreadVisible: " + (threadId == visibleThread) + ")");
}
} else {
warn(content.getTimestamp(), "Could not find a thread for the pending message. Using current time for received time.");
}
}
return receivedTime;
}
private void handlePayment(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) {
if (!message.getPayment().isPresent()) {
throw new AssertionError();
@@ -611,6 +650,7 @@ public final class MessageContentProcessor {
content.getSenderDevice(),
content.getTimestamp(),
content.getServerReceivedTimestamp(),
System.currentTimeMillis(),
"",
Optional.absent(),
0,
@@ -667,13 +707,14 @@ public final class MessageContentProcessor {
private void handleGroupV1Message(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message,
@NonNull Optional<Long> smsMessageId,
@NonNull GroupId.V1 groupId)
@NonNull GroupId.V1 groupId,
long receivedTime)
throws StorageFailedException, BadGroupIdException
{
GroupV1MessageProcessor.process(context, content, message, false);
if (message.getExpiresInSeconds() != 0 && message.getExpiresInSeconds() != getMessageDestination(content, message).getExpireMessages()) {
handleExpirationUpdate(content, message, Optional.absent(), Optional.of(groupId));
handleExpirationUpdate(content, message, Optional.absent(), Optional.of(groupId), receivedTime);
}
if (smsMessageId.isPresent()) {
@@ -703,7 +744,8 @@ public final class MessageContentProcessor {
private void handleExpirationUpdate(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message,
@NonNull Optional<Long> smsMessageId,
@NonNull Optional<GroupId> groupId)
@NonNull Optional<GroupId> groupId,
long receivedTime)
throws StorageFailedException, BadGroupIdException
{
if (groupId.isPresent() && groupId.get().isV2()) {
@@ -726,6 +768,7 @@ public final class MessageContentProcessor {
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(sender.getId(),
content.getTimestamp(),
content.getServerReceivedTimestamp(),
receivedTime,
-1,
expiresInSeconds * 1000L,
true,
@@ -1150,7 +1193,8 @@ public final class MessageContentProcessor {
private void handleMediaMessage(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message,
@NonNull Optional<Long> smsMessageId)
@NonNull Optional<Long> smsMessageId,
long receivedTime)
throws StorageFailedException, BadGroupIdException
{
notifyTypingStoppedFromIncomingMessage(getMessageDestination(content, message), content.getSender(), content.getSenderDevice());
@@ -1170,6 +1214,7 @@ public final class MessageContentProcessor {
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(RecipientId.fromHighTrust(content.getSender()),
message.getTimestamp(),
content.getServerReceivedTimestamp(),
receivedTime,
-1,
message.getExpiresInSeconds() * 1000L,
false,
@@ -1373,7 +1418,8 @@ public final class MessageContentProcessor {
private void handleTextMessage(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message,
@NonNull Optional<Long> smsMessageId,
@NonNull Optional<GroupId> groupId)
@NonNull Optional<GroupId> groupId,
long receivedTime)
throws StorageFailedException, BadGroupIdException
{
MessageDatabase database = DatabaseFactory.getSmsDatabase(context);
@@ -1381,7 +1427,7 @@ public final class MessageContentProcessor {
Recipient recipient = getMessageDestination(content, message);
if (message.getExpiresInSeconds() != recipient.getExpireMessages()) {
handleExpirationUpdate(content, message, Optional.absent(), groupId);
handleExpirationUpdate(content, message, Optional.absent(), groupId, receivedTime);
}
Long threadId;
@@ -1395,6 +1441,7 @@ public final class MessageContentProcessor {
content.getSenderDevice(),
message.getTimestamp(),
content.getServerReceivedTimestamp(),
receivedTime,
body,
groupId,
message.getExpiresInSeconds() * 1000L,
@@ -1603,12 +1650,13 @@ public final class MessageContentProcessor {
return;
}
log("Processing viewed reciepts for IDs: " + Util.join(message.getTimestamps(), ","));
Recipient sender = Recipient.externalHighTrustPush(context, content.getSender());
log(TAG, "Processing viewed receipts. Sender: " + sender.getId() + ", Device: " + content.getSenderDevice() + ", Timestamps: " + Util.join(message.getTimestamps(), ", "));
List<SyncMessageId> ids = Stream.of(message.getTimestamps())
.map(t -> new SyncMessageId(sender.getId(), t))
.toList();
Recipient sender = Recipient.externalHighTrustPush(context, content.getSender());
List<SyncMessageId> ids = Stream.of(message.getTimestamps())
.map(t -> new SyncMessageId(sender.getId(), t))
.toList();
Collection<SyncMessageId> unhandled = DatabaseFactory.getMmsSmsDatabase(context)
.incrementViewedReceiptCounts(ids, content.getTimestamp());
@@ -1622,12 +1670,12 @@ public final class MessageContentProcessor {
private void handleDeliveryReceipt(@NonNull SignalServiceContent content,
@NonNull SignalServiceReceiptMessage message)
{
log(TAG, "Processing delivery receipts for IDs: " + Util.join(message.getTimestamps(), ", "));
Recipient sender = Recipient.externalHighTrustPush(context, content.getSender());
log(TAG, "Processing delivery receipts. Sender: " + sender.getId() + ", Device: " + content.getSenderDevice() + ", Timestamps: " + Util.join(message.getTimestamps(), ", "));
Recipient sender = Recipient.externalHighTrustPush(context, content.getSender());
List<SyncMessageId> ids = Stream.of(message.getTimestamps())
.map(t -> new SyncMessageId(sender.getId(), t))
.toList();
List<SyncMessageId> ids = Stream.of(message.getTimestamps())
.map(t -> new SyncMessageId(sender.getId(), t))
.toList();
DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCounts(ids, System.currentTimeMillis());
DatabaseFactory.getMessageLogDatabase(context).deleteEntriesForRecipient(message.getTimestamps(), sender.getId(), content.getSenderDevice());
@@ -1642,12 +1690,12 @@ public final class MessageContentProcessor {
return;
}
log("Processing read receipts for IDs: " + Util.join(message.getTimestamps(), ", "));
Recipient sender = Recipient.externalHighTrustPush(context, content.getSender());
log(TAG, "Processing read receipts. Sender: " + sender.getId() + ", Device: " + content.getSenderDevice() + ", Timestamps: " + Util.join(message.getTimestamps(), ", "));
Recipient sender = Recipient.externalHighTrustPush(context, content.getSender());
List<SyncMessageId> ids = Stream.of(message.getTimestamps())
.map(t -> new SyncMessageId(sender.getId(), t))
.toList();
List<SyncMessageId> ids = Stream.of(message.getTimestamps())
.map(t -> new SyncMessageId(sender.getId(), t))
.toList();
Collection<SyncMessageId> unhandled = DatabaseFactory.getMmsSmsDatabase(context).incrementReadReceiptCounts(ids, content.getTimestamp());
@@ -2016,7 +2064,7 @@ public final class MessageContentProcessor {
private Optional<InsertResult> insertPlaceholder(@NonNull String sender, int senderDevice, long timestamp, Optional<GroupId> groupId) {
MessageDatabase database = DatabaseFactory.getSmsDatabase(context);
IncomingTextMessage textMessage = new IncomingTextMessage(Recipient.external(context, sender).getId(),
senderDevice, timestamp, -1, "",
senderDevice, timestamp, -1, System.currentTimeMillis(), "",
groupId, 0, false, null);
textMessage = new IncomingEncryptedMessage(textMessage, "");
@@ -2029,6 +2077,11 @@ public final class MessageContentProcessor {
return getGroupRecipient(message.getMessage().getGroupContext()).or(() -> Recipient.externalPush(context, message.getDestination().get()));
}
private Recipient getMessageDestination(@NonNull SignalServiceContent content) throws BadGroupIdException {
SignalServiceDataMessage message = content.getDataMessage().orNull();
return getGroupRecipient(message != null ? message.getGroupContext() : Optional.absent()).or(() -> Recipient.externalHighTrustPush(context, content.getSender()));
}
private Recipient getMessageDestination(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message)
throws BadGroupIdException
@@ -2056,7 +2109,7 @@ public final class MessageContentProcessor {
Recipient author = Recipient.externalPush(context, sender);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(conversationRecipient);
if (threadId > 0) {
if (threadId > 0 && TextSecurePreferences.isTypingIndicatorsEnabled(context)) {
Log.d(TAG, "Typing stopped on thread " + threadId + " due to an incoming message.");
ApplicationDependencies.getTypingStatusRepository().onTypingStopped(context, threadId, author, device, true);
}

View File

@@ -26,8 +26,9 @@ public class IncomingMediaMessage {
private final String body;
private final boolean push;
private final long sentTimeMillis;
private final long serverTimeMillis;
private final int subscriptionId;
private final long serverTimeMillis;
private final long receivedTimeMillis;
private final int subscriptionId;
private final long expiresIn;
private final boolean expirationUpdate;
private final QuoteModel quote;
@@ -45,6 +46,7 @@ public class IncomingMediaMessage {
String body,
long sentTimeMillis,
long serverTimeMillis,
long receivedTimeMillis,
List<Attachment> attachments,
int subscriptionId,
long expiresIn,
@@ -53,19 +55,20 @@ public class IncomingMediaMessage {
boolean unidentified,
Optional<List<Contact>> sharedContacts)
{
this.from = from;
this.groupId = groupId.orNull();
this.sentTimeMillis = sentTimeMillis;
this.serverTimeMillis = serverTimeMillis;
this.body = body;
this.push = false;
this.subscriptionId = subscriptionId;
this.expiresIn = expiresIn;
this.expirationUpdate = expirationUpdate;
this.viewOnce = viewOnce;
this.quote = null;
this.unidentified = unidentified;
this.serverGuid = null;
this.from = from;
this.groupId = groupId.orNull();
this.sentTimeMillis = sentTimeMillis;
this.serverTimeMillis = serverTimeMillis;
this.receivedTimeMillis = receivedTimeMillis;
this.body = body;
this.push = false;
this.subscriptionId = subscriptionId;
this.expiresIn = expiresIn;
this.expirationUpdate = expirationUpdate;
this.viewOnce = viewOnce;
this.quote = null;
this.unidentified = unidentified;
this.serverGuid = null;
this.attachments.addAll(attachments);
this.sharedContacts.addAll(sharedContacts.or(Collections.emptyList()));
@@ -75,6 +78,7 @@ public class IncomingMediaMessage {
public IncomingMediaMessage(@NonNull RecipientId from,
long sentTimeMillis,
long serverTimeMillis,
long receivedTimeMillis,
int subscriptionId,
long expiresIn,
boolean expirationUpdate,
@@ -90,17 +94,18 @@ public class IncomingMediaMessage {
Optional<Attachment> sticker,
@Nullable String serverGuid)
{
this.push = true;
this.from = from;
this.sentTimeMillis = sentTimeMillis;
this.serverTimeMillis = serverTimeMillis;
this.body = body.orNull();
this.subscriptionId = subscriptionId;
this.expiresIn = expiresIn;
this.expirationUpdate = expirationUpdate;
this.viewOnce = viewOnce;
this.quote = quote.orNull();
this.unidentified = unidentified;
this.push = true;
this.from = from;
this.sentTimeMillis = sentTimeMillis;
this.serverTimeMillis = serverTimeMillis;
this.receivedTimeMillis = receivedTimeMillis;
this.body = body.orNull();
this.subscriptionId = subscriptionId;
this.expiresIn = expiresIn;
this.expirationUpdate = expirationUpdate;
this.viewOnce = viewOnce;
this.quote = quote.orNull();
this.unidentified = unidentified;
if (group.isPresent()) this.groupId = GroupUtil.idFromGroupContextOrThrow(group.get());
else this.groupId = null;
@@ -153,6 +158,10 @@ public class IncomingMediaMessage {
return serverTimeMillis;
}
public long getReceivedTimeMillis() {
return receivedTimeMillis;
}
public long getExpiresIn() {
return expiresIn;
}

View File

@@ -6,7 +6,7 @@ import org.whispersystems.libsignal.util.guava.Optional;
public class IncomingJoinedMessage extends IncomingTextMessage {
public IncomingJoinedMessage(RecipientId sender) {
super(sender, 1, System.currentTimeMillis(), -1, null, Optional.absent(), 0, false, null);
super(sender, 1, System.currentTimeMillis(), -1, System.currentTimeMillis(), null, Optional.absent(), 0, false, null);
}
@Override

View File

@@ -39,6 +39,7 @@ public class IncomingTextMessage implements Parcelable {
private final String pseudoSubject;
private final long sentTimestampMillis;
private final long serverTimestampMillis;
private final long receivedTimestampMillis;
@Nullable private final GroupId groupId;
private final boolean push;
private final int subscriptionId;
@@ -47,84 +48,89 @@ public class IncomingTextMessage implements Parcelable {
@Nullable private final String serverGuid;
public IncomingTextMessage(@NonNull RecipientId sender, @NonNull SmsMessage message, int subscriptionId) {
this.message = message.getDisplayMessageBody();
this.sender = sender;
this.senderDeviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
this.protocol = message.getProtocolIdentifier();
this.serviceCenterAddress = message.getServiceCenterAddress();
this.replyPathPresent = message.isReplyPathPresent();
this.pseudoSubject = message.getPseudoSubject();
this.sentTimestampMillis = message.getTimestampMillis();
this.serverTimestampMillis = -1;
this.subscriptionId = subscriptionId;
this.expiresInMillis = 0;
this.groupId = null;
this.push = false;
this.unidentified = false;
this.serverGuid = null;
this.message = message.getDisplayMessageBody();
this.sender = sender;
this.senderDeviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
this.protocol = message.getProtocolIdentifier();
this.serviceCenterAddress = message.getServiceCenterAddress();
this.replyPathPresent = message.isReplyPathPresent();
this.pseudoSubject = message.getPseudoSubject();
this.sentTimestampMillis = message.getTimestampMillis();
this.serverTimestampMillis = -1;
this.receivedTimestampMillis = System.currentTimeMillis();
this.subscriptionId = subscriptionId;
this.expiresInMillis = 0;
this.groupId = null;
this.push = false;
this.unidentified = false;
this.serverGuid = null;
}
public IncomingTextMessage(@NonNull RecipientId sender,
int senderDeviceId,
long sentTimestampMillis,
long serverTimestampMillis,
long receivedTimestampMillis,
String encodedBody,
Optional<GroupId> groupId,
long expiresInMillis,
boolean unidentified,
String serverGuid)
{
this.message = encodedBody;
this.sender = sender;
this.senderDeviceId = senderDeviceId;
this.protocol = 31337;
this.serviceCenterAddress = "GCM";
this.replyPathPresent = true;
this.pseudoSubject = "";
this.sentTimestampMillis = sentTimestampMillis;
this.serverTimestampMillis = serverTimestampMillis;
this.push = true;
this.subscriptionId = -1;
this.expiresInMillis = expiresInMillis;
this.unidentified = unidentified;
this.groupId = groupId.orNull();
this.serverGuid = serverGuid;
this.message = encodedBody;
this.sender = sender;
this.senderDeviceId = senderDeviceId;
this.protocol = 31337;
this.serviceCenterAddress = "GCM";
this.replyPathPresent = true;
this.pseudoSubject = "";
this.sentTimestampMillis = sentTimestampMillis;
this.serverTimestampMillis = serverTimestampMillis;
this.receivedTimestampMillis = receivedTimestampMillis;
this.push = true;
this.subscriptionId = -1;
this.expiresInMillis = expiresInMillis;
this.unidentified = unidentified;
this.groupId = groupId.orNull();
this.serverGuid = serverGuid;
}
public IncomingTextMessage(Parcel in) {
this.message = in.readString();
this.sender = in.readParcelable(IncomingTextMessage.class.getClassLoader());
this.senderDeviceId = in.readInt();
this.protocol = in.readInt();
this.serviceCenterAddress = in.readString();
this.replyPathPresent = (in.readInt() == 1);
this.pseudoSubject = in.readString();
this.sentTimestampMillis = in.readLong();
this.serverTimestampMillis = in.readLong();
this.groupId = GroupId.parseNullableOrThrow(in.readString());
this.push = (in.readInt() == 1);
this.subscriptionId = in.readInt();
this.expiresInMillis = in.readLong();
this.unidentified = in.readInt() == 1;
this.serverGuid = in.readString();
this.message = in.readString();
this.sender = in.readParcelable(IncomingTextMessage.class.getClassLoader());
this.senderDeviceId = in.readInt();
this.protocol = in.readInt();
this.serviceCenterAddress = in.readString();
this.replyPathPresent = (in.readInt() == 1);
this.pseudoSubject = in.readString();
this.sentTimestampMillis = in.readLong();
this.serverTimestampMillis = in.readLong();
this.receivedTimestampMillis = in.readLong();
this.groupId = GroupId.parseNullableOrThrow(in.readString());
this.push = (in.readInt() == 1);
this.subscriptionId = in.readInt();
this.expiresInMillis = in.readLong();
this.unidentified = in.readInt() == 1;
this.serverGuid = in.readString();
}
public IncomingTextMessage(IncomingTextMessage base, String newBody) {
this.message = newBody;
this.sender = base.getSender();
this.senderDeviceId = base.getSenderDeviceId();
this.protocol = base.getProtocol();
this.serviceCenterAddress = base.getServiceCenterAddress();
this.replyPathPresent = base.isReplyPathPresent();
this.pseudoSubject = base.getPseudoSubject();
this.sentTimestampMillis = base.getSentTimestampMillis();
this.serverTimestampMillis = base.getServerTimestampMillis();
this.groupId = base.getGroupId();
this.push = base.isPush();
this.subscriptionId = base.getSubscriptionId();
this.expiresInMillis = base.getExpiresIn();
this.unidentified = base.isUnidentified();
this.serverGuid = base.getServerGuid();
this.message = newBody;
this.sender = base.getSender();
this.senderDeviceId = base.getSenderDeviceId();
this.protocol = base.getProtocol();
this.serviceCenterAddress = base.getServiceCenterAddress();
this.replyPathPresent = base.isReplyPathPresent();
this.pseudoSubject = base.getPseudoSubject();
this.sentTimestampMillis = base.getSentTimestampMillis();
this.serverTimestampMillis = base.getServerTimestampMillis();
this.receivedTimestampMillis = base.getReceivedTimestampMillis();
this.groupId = base.getGroupId();
this.push = base.isPush();
this.subscriptionId = base.getSubscriptionId();
this.expiresInMillis = base.getExpiresIn();
this.unidentified = base.isUnidentified();
this.serverGuid = base.getServerGuid();
}
public IncomingTextMessage(List<IncomingTextMessage> fragments) {
@@ -134,40 +140,42 @@ public class IncomingTextMessage implements Parcelable {
body.append(message.getMessageBody());
}
this.message = body.toString();
this.sender = fragments.get(0).getSender();
this.senderDeviceId = fragments.get(0).getSenderDeviceId();
this.protocol = fragments.get(0).getProtocol();
this.serviceCenterAddress = fragments.get(0).getServiceCenterAddress();
this.replyPathPresent = fragments.get(0).isReplyPathPresent();
this.pseudoSubject = fragments.get(0).getPseudoSubject();
this.sentTimestampMillis = fragments.get(0).getSentTimestampMillis();
this.serverTimestampMillis = fragments.get(0).getServerTimestampMillis();
this.groupId = fragments.get(0).getGroupId();
this.push = fragments.get(0).isPush();
this.subscriptionId = fragments.get(0).getSubscriptionId();
this.expiresInMillis = fragments.get(0).getExpiresIn();
this.unidentified = fragments.get(0).isUnidentified();
this.serverGuid = fragments.get(0).getServerGuid();
this.message = body.toString();
this.sender = fragments.get(0).getSender();
this.senderDeviceId = fragments.get(0).getSenderDeviceId();
this.protocol = fragments.get(0).getProtocol();
this.serviceCenterAddress = fragments.get(0).getServiceCenterAddress();
this.replyPathPresent = fragments.get(0).isReplyPathPresent();
this.pseudoSubject = fragments.get(0).getPseudoSubject();
this.sentTimestampMillis = fragments.get(0).getSentTimestampMillis();
this.serverTimestampMillis = fragments.get(0).getServerTimestampMillis();
this.receivedTimestampMillis = fragments.get(0).getReceivedTimestampMillis();
this.groupId = fragments.get(0).getGroupId();
this.push = fragments.get(0).isPush();
this.subscriptionId = fragments.get(0).getSubscriptionId();
this.expiresInMillis = fragments.get(0).getExpiresIn();
this.unidentified = fragments.get(0).isUnidentified();
this.serverGuid = fragments.get(0).getServerGuid();
}
protected IncomingTextMessage(@NonNull RecipientId sender, @Nullable GroupId groupId)
{
this.message = "";
this.sender = sender;
this.senderDeviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
this.protocol = 31338;
this.serviceCenterAddress = "Outgoing";
this.replyPathPresent = true;
this.pseudoSubject = "";
this.sentTimestampMillis = System.currentTimeMillis();
this.serverTimestampMillis = sentTimestampMillis;
this.groupId = groupId;
this.push = true;
this.subscriptionId = -1;
this.expiresInMillis = 0;
this.unidentified = false;
this.serverGuid = null;
this.message = "";
this.sender = sender;
this.senderDeviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
this.protocol = 31338;
this.serviceCenterAddress = "Outgoing";
this.replyPathPresent = true;
this.pseudoSubject = "";
this.sentTimestampMillis = System.currentTimeMillis();
this.serverTimestampMillis = sentTimestampMillis;
this.receivedTimestampMillis = sentTimestampMillis;
this.groupId = groupId;
this.push = true;
this.subscriptionId = -1;
this.expiresInMillis = 0;
this.unidentified = false;
this.serverGuid = null;
}
public int getSubscriptionId() {
@@ -186,6 +194,10 @@ public class IncomingTextMessage implements Parcelable {
return serverTimestampMillis;
}
public long getReceivedTimestampMillis() {
return receivedTimestampMillis;
}
public String getPseudoSubject() {
return pseudoSubject;
}

View File

@@ -74,7 +74,7 @@ public final class IdentityUtil {
if (groupRecord.getMembers().contains(recipient.getId()) && groupRecord.isActive() && !groupRecord.isMms()) {
if (remote) {
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getId(), 1, time, -1, null, Optional.of(groupRecord.getId()), 0, false, null);
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getId(), 1, time, -1, time, null, Optional.of(groupRecord.getId()), 0, false, null);
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
else incoming = new IncomingIdentityDefaultMessage(incoming);
@@ -96,7 +96,7 @@ public final class IdentityUtil {
}
if (remote) {
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getId(), 1, time, -1, null, Optional.absent(), 0, false, null);
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getId(), 1, time, -1, time, null, Optional.absent(), 0, false, null);
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
else incoming = new IncomingIdentityDefaultMessage(incoming);
@@ -125,7 +125,7 @@ public final class IdentityUtil {
while ((groupRecord = reader.getNext()) != null) {
if (groupRecord.getMembers().contains(recipientId) && groupRecord.isActive()) {
IncomingTextMessage incoming = new IncomingTextMessage(recipientId, 1, time, time, null, Optional.of(groupRecord.getId()), 0, false, null);
IncomingTextMessage incoming = new IncomingTextMessage(recipientId, 1, time, time, time, null, Optional.of(groupRecord.getId()), 0, false, null);
IncomingIdentityUpdateMessage groupUpdate = new IncomingIdentityUpdateMessage(incoming);
smsDatabase.insertMessageInbox(groupUpdate);
@@ -133,7 +133,7 @@ public final class IdentityUtil {
}
}
IncomingTextMessage incoming = new IncomingTextMessage(recipientId, 1, time, -1, null, Optional.absent(), 0, false, null);
IncomingTextMessage incoming = new IncomingTextMessage(recipientId, 1, time, -1, time, null, Optional.absent(), 0, false, null);
IncomingIdentityUpdateMessage individualUpdate = new IncomingIdentityUpdateMessage(incoming);
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(individualUpdate);

View File

@@ -2459,6 +2459,8 @@
<string name="preferences__internal_click_to_delete_all_sharing_state" translatable="false">Click to delete all sharing state</string>
<string name="preferences__internal_remove_two_person_minimum" translatable="false">Remove 2 person minimum</string>
<string name="preferences__internal_remove_the_requirement_that_you_need" translatable="false">Remove the requirement that you need at least 2 recipients to use sender key.</string>
<string name="preferences__internal_delay_resends" translatable="false">Delay resends</string>
<string name="preferences__internal_delay_resending_messages_in_response_to_retry_receipts" translatable="false">Delay resending messages in response to retry receipts by 10 seconds.</string>
<string name="preferences__internal_calling" translatable="false">Group call server</string>
<string name="preferences__internal_calling_default" translatable="false">Default</string>
<string name="preferences__internal_calling_s_server" translatable="false">%1$s server</string>

View File

@@ -1753,7 +1753,7 @@ public class SignalServiceMessageSender {
.collect(Collectors.toList());
Set<String> successUuids = successes.stream().map(a -> a.getUuid().get().toString()).collect(Collectors.toSet());
Set<SignalProtocolAddress> successAddresses = destinations.stream().filter(a -> successUuids.contains(a.getName())).collect(Collectors.toSet());;
Set<SignalProtocolAddress> successAddresses = destinations.stream().filter(a -> successUuids.contains(a.getName())).collect(Collectors.toSet());
store.markSenderKeySharedWith(distributionId, successAddresses);