diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt index 2a88184918..3c4c20926c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt index 377fb17735..c18a3894b9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt @@ -15,4 +15,5 @@ data class InternalSettingsState( val useBuiltInEmojiSet: Boolean, val emojiVersion: EmojiFiles.Version?, val removeSenderKeyMinimium: Boolean, + val delayResends: Boolean, ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt index 3e0ffa009a..bc205340ec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt @@ -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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index 7807a29477..b76f607b9d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -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); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index e4b4d1e3bc..8c20e32b0f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -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()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupV1MessageProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupV1MessageProcessor.java index eebf8decca..3e6c5ac47f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupV1MessageProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupV1MessageProcessor.java @@ -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 = smsDatabase.insertMessageInbox(groupMessage); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java index 6ba4f2781c..735af59cea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java @@ -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()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java index a61b402883..618324ee59 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java @@ -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 = database.insertMessageInbox(message, contentLocation, threadId); if (insertResult.isPresent()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ResendMessageJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ResendMessageJob.java index 6a35e18b28..4a08428f43 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ResendMessageJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ResendMessageJob.java @@ -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 addresses = result.getSuccess() + .getDevices() + .stream() + .map(device -> new SignalProtocolAddress(recipient.requireServiceId(), device)) + .collect(Collectors.toList()); + + new SignalSenderKeyStore(context).markSenderKeySharedWith(distributionId, addresses); + } } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/SenderKeyDistributionSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/SenderKeyDistributionSendJob.java index 4ded2905d4..374924cced 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/SenderKeyDistributionSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/SenderKeyDistributionSendJob.java @@ -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> 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 addresses = result.getSuccess() + .getDevices() + .stream() + .map(device -> new SignalProtocolAddress(recipient.requireServiceId(), device)) + .collect(Collectors.toList()); + + new SignalSenderKeyStore(context).markSenderKeySharedWith(distributionId, addresses); + } } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.java index 82d1735bfa..0f7710f2da 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.java @@ -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. diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java b/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java index 683c9aa429..ec4892a547 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java @@ -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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageProcessor.java index 2a649247c7..4dd407f901 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageProcessor.java @@ -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()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java index ed86d16624..91fd885efc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java @@ -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 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 smsMessageId, - @NonNull Optional groupId) + @NonNull Optional 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 smsMessageId) + @NonNull Optional 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 smsMessageId, - @NonNull Optional groupId) + @NonNull Optional 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 ids = Stream.of(message.getTimestamps()) + .map(t -> new SyncMessageId(sender.getId(), t)) + .toList(); - Recipient sender = Recipient.externalHighTrustPush(context, content.getSender()); - List ids = Stream.of(message.getTimestamps()) - .map(t -> new SyncMessageId(sender.getId(), t)) - .toList(); Collection 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 ids = Stream.of(message.getTimestamps()) - .map(t -> new SyncMessageId(sender.getId(), t)) - .toList(); + List 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 ids = Stream.of(message.getTimestamps()) - .map(t -> new SyncMessageId(sender.getId(), t)) - .toList(); + List ids = Stream.of(message.getTimestamps()) + .map(t -> new SyncMessageId(sender.getId(), t)) + .toList(); Collection unhandled = DatabaseFactory.getMmsSmsDatabase(context).incrementReadReceiptCounts(ids, content.getTimestamp()); @@ -2016,7 +2064,7 @@ public final class MessageContentProcessor { private Optional insertPlaceholder(@NonNull String sender, int senderDevice, long timestamp, Optional 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); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java b/app/src/main/java/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java index 1a5dddd6ab..716335f519 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java @@ -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 attachments, int subscriptionId, long expiresIn, @@ -53,19 +55,20 @@ public class IncomingMediaMessage { boolean unidentified, Optional> 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 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; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingJoinedMessage.java b/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingJoinedMessage.java index 22653794cf..62377359af 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingJoinedMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingJoinedMessage.java @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingTextMessage.java b/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingTextMessage.java index c4a1f9ca13..9104684118 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingTextMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingTextMessage.java @@ -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, 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 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; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java index cc3caac97b..e6ca8bd856 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java @@ -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 = smsDatabase.insertMessageInbox(individualUpdate); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f0a23f3ea1..f837f3c32b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2459,6 +2459,8 @@ Click to delete all sharing state Remove 2 person minimum Remove the requirement that you need at least 2 recipients to use sender key. + Delay resends + Delay resending messages in response to retry receipts by 10 seconds. Group call server Default %1$s server diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java index d7081eb1c9..428e051a8e 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java @@ -1753,7 +1753,7 @@ public class SignalServiceMessageSender { .collect(Collectors.toList()); Set successUuids = successes.stream().map(a -> a.getUuid().get().toString()).collect(Collectors.toSet()); - Set successAddresses = destinations.stream().filter(a -> successUuids.contains(a.getName())).collect(Collectors.toSet());; + Set successAddresses = destinations.stream().filter(a -> successUuids.contains(a.getName())).collect(Collectors.toSet()); store.markSenderKeySharedWith(distributionId, successAddresses);