diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt index c63c196260..c8ec9e5ee3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt @@ -14,6 +14,7 @@ import org.session.libsession.utilities.ExpirationUtil import org.session.libsession.utilities.SSKEnvironment.MessageExpirationManagerProtocol import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getExpirationTypeDisplayValue +import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities @@ -25,11 +26,11 @@ class DisappearingMessages @Inject constructor( private val textSecurePreferences: TextSecurePreferences, private val messageExpirationManager: MessageExpirationManagerProtocol, ) { - fun set(threadId: Long, address: Address, mode: ExpiryMode) { + fun set(threadId: Long, address: Address, mode: ExpiryMode, isGroup: Boolean) { val expiryChangeTimestampMs = SnodeAPI.nowWithOffset MessagingModuleConfiguration.shared.storage.setExpirationConfiguration(ExpirationConfiguration(threadId, mode, expiryChangeTimestampMs)) - val message = ExpirationTimerUpdate().apply { + val message = ExpirationTimerUpdate(isGroup = isGroup).apply { expiryMode = mode sender = textSecurePreferences.getLocalNumber() isSenderSelf = true @@ -62,7 +63,7 @@ class DisappearingMessages @Inject constructor( text = if (message.expiresIn == 0L) R.string.dialog_disappearing_messages_follow_setting_confirm else R.string.dialog_disappearing_messages_follow_setting_set, contentDescription = if (message.expiresIn == 0L) R.string.AccessibilityId_confirm else R.string.AccessibilityId_set_button ) { - set(message.threadId, message.recipient.address, message.expiryMode) + set(message.threadId, message.recipient.address, message.expiryMode, message.recipient.isClosedGroupRecipient) } cancelButton() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModel.kt index 79ede208ad..8e023b5a42 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModel.kt @@ -87,7 +87,7 @@ class DisappearingMessagesViewModel( return@launch } - disappearingMessages.set(threadId, address, mode) + disappearingMessages.set(threadId, address, mode, state.isGroup) _event.send(Event.SUCCESS) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 201c6c94dd..5690ccc2c3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -72,6 +72,7 @@ import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.mentions.Mention import org.session.libsession.messaging.mentions.MentionsManager import org.session.libsession.messaging.messages.ExpirationConfiguration +import org.session.libsession.messaging.messages.applyExpiryMode import org.session.libsession.messaging.messages.control.DataExtractionNotification import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage @@ -1574,7 +1575,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe return null } // Create the message - val message = VisibleMessage() + val message = VisibleMessage().applyExpiryMode(viewModel.threadId) message.sentTimestamp = sentTimestamp message.text = text val expiresInMillis = viewModel.expirationConfiguration?.expiryMode?.expiryMillis ?: 0 @@ -1609,7 +1610,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe val sentTimestamp = SnodeAPI.nowWithOffset processMessageRequestApproval() // Create the message - val message = VisibleMessage() + val message = VisibleMessage().applyExpiryMode() message.sentTimestamp = sentTimestamp message.text = body val quote = quotedMessage?.let { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.kt index 257d30866b..d173dacfef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.kt @@ -30,6 +30,10 @@ class ExpirationTimerView @JvmOverloads constructor( R.drawable.timer60 ) + fun setTimerIcon() { + setExpirationTime(0L, 0L) + } + fun setExpirationTime(startedAt: Long, expiresIn: Long) { if (expiresIn == 0L) { setImageResource(R.drawable.timer55) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt index d202b3ad13..4c471b6443 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt @@ -53,12 +53,19 @@ class ControlMessageView : LinearLayout { Log.d(TAG, "bind() called, messageBody = $messageBody") - expirationTimerView.setExpirationTime(message.expireStarted, message.expiresIn) + val threadRecipient = DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(message.threadId) + + if (threadRecipient?.isClosedGroupRecipient == true) { + expirationTimerView.setTimerIcon() + } else { + expirationTimerView.setExpirationTime(message.expireStarted, message.expiresIn) + } + followSetting.isVisible = ExpirationConfiguration.isNewConfigEnabled && !message.isOutgoing && message.expiryMode != (MessagingModuleConfiguration.shared.storage.getExpirationConfiguration(message.threadId)?.expiryMode ?: ExpiryMode.NONE) - && DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(message.threadId)?.isGroupRecipient != true + && threadRecipient?.isGroupRecipient != true followSetting.setOnClickListener { disappearingMessages.showFollowSettingDialog(context, message) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MarkedMessageInfo.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MarkedMessageInfo.kt index 4f32077d44..9de3dac695 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MarkedMessageInfo.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MarkedMessageInfo.kt @@ -4,11 +4,11 @@ import org.thoughtcrime.securesms.conversation.disappearingmessages.ExpiryType import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId data class MarkedMessageInfo(val syncMessageId: SyncMessageId, val expirationInfo: ExpirationInfo) { - fun guessExpiryType(): ExpiryType = expirationInfo.run { - when { - syncMessageId.timetamp == expireStarted -> ExpiryType.AFTER_SEND - expiresIn > 0 -> ExpiryType.AFTER_READ - else -> ExpiryType.NONE - } + val expiryType get() = when { + syncMessageId.timetamp == expirationInfo.expireStarted -> ExpiryType.AFTER_SEND + expirationInfo.expiresIn > 0 -> ExpiryType.AFTER_READ + else -> ExpiryType.NONE } + + val expiryMode get() = expiryType.mode(expirationInfo.expiresIn) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index dca386a7b6..5e3cc46305 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -382,9 +382,6 @@ open class Storage( DatabaseComponent.get(context).lokiMessageDatabase().setMessageServerHash(id, message.isMediaMessage(), serverHash) } } - if (expiryMode is ExpiryMode.AfterSend) { - SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(message.sentTimestamp!!, message.sender!!, expireStartedAt) - } return messageID } @@ -972,26 +969,24 @@ open class Storage( val recipient = Recipient.from(context, fromSerialized(groupID), false) val threadId = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient) val expirationConfig = getExpirationConfiguration(threadId) - val expiryMode = expirationConfig?.expiryMode - val expiresInMillis = expiryMode?.expiryMillis ?: 0 + val expiryMode = expirationConfig?.expiryMode ?: ExpiryMode.NONE + val expiresInMillis = expiryMode.expiryMillis val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0 val m = IncomingTextMessage(fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), expiresInMillis, expireStartedAt, true, false) val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() val infoMessage = IncomingGroupMessage(m, groupID, updateData, true) val smsDB = DatabaseComponent.get(context).smsDatabase() smsDB.insertMessageInbox(infoMessage, true) - if (expiryMode is ExpiryMode.AfterSend) { - SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(sentTimestamp, senderPublicKey, expireStartedAt) - } + SSKEnvironment.shared.messageExpirationManager.maybeStartExpiration(sentTimestamp, senderPublicKey, expiryMode) } override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, threadID: Long, sentTimestamp: Long) { - val userPublicKey = getUserPublicKey() + val userPublicKey = getUserPublicKey()!! val recipient = Recipient.from(context, fromSerialized(groupID), false) val threadId = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient) val expirationConfig = getExpirationConfiguration(threadId) - val expiryMode = expirationConfig?.expiryMode - val expiresInMillis = expiryMode?.expiryMillis ?: 0 + val expiryMode = expirationConfig?.expiryMode ?: ExpiryMode.NONE + val expiresInMillis = expiryMode.expiryMillis val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0 val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() ?: "" val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, expiresInMillis, expireStartedAt, true, null, listOf(), listOf()) @@ -1000,9 +995,7 @@ open class Storage( if (mmsSmsDB.getMessageFor(sentTimestamp, userPublicKey) != null) return val infoMessageID = mmsDB.insertMessageOutbox(infoMessage, threadID, false, null, runThreadUpdate = true) mmsDB.markAsSent(infoMessageID, true) - if (expiryMode is ExpiryMode.AfterSend) { - SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(sentTimestamp, userPublicKey!!, expireStartedAt) - } + SSKEnvironment.shared.messageExpirationManager.maybeStartExpiration(sentTimestamp, userPublicKey, expiryMode) } override fun isClosedGroup(publicKey: String): Boolean { @@ -1403,8 +1396,8 @@ open class Storage( if (recipient.isBlocked) return val threadId = getThreadId(recipient) ?: return val expirationConfig = getExpirationConfiguration(threadId) - val expiryMode = expirationConfig?.expiryMode - val expiresInMillis = expiryMode?.expiryMillis ?: 0 + val expiryMode = expirationConfig?.expiryMode ?: ExpiryMode.NONE + val expiresInMillis = expiryMode.expiryMillis val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0 val mediaMessage = IncomingMediaMessage( address, @@ -1426,9 +1419,8 @@ open class Storage( ) database.insertSecureDecryptedMessageInbox(mediaMessage, threadId, runThreadUpdate = true) - if (expiryMode is ExpiryMode.AfterSend) { - SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(sentTimestamp, senderPublicKey, expireStartedAt) - } + + SSKEnvironment.shared.messageExpirationManager.maybeStartExpiration(sentTimestamp, senderPublicKey, expiryMode) } override fun insertMessageRequestResponse(response: MessageRequestResponse) { @@ -1557,14 +1549,12 @@ open class Storage( val recipient = Recipient.from(context, address, false) val threadId = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient) val expirationConfig = getExpirationConfiguration(threadId) - val expiryMode = expirationConfig?.expiryMode - val expiresInMillis = expiryMode?.expiryMillis ?: 0 + val expiryMode = expirationConfig?.expiryMode?.coerceSendToRead() ?: ExpiryMode.NONE + val expiresInMillis = expiryMode.expiryMillis val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0 val callMessage = IncomingTextMessage.fromCallInfo(callMessageType, address, Optional.absent(), sentTimestamp, expiresInMillis, expireStartedAt) database.insertCallMessage(callMessage) - if (expiryMode is ExpiryMode.AfterSend) { - SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(sentTimestamp, senderPublicKey, expireStartedAt) - } + SSKEnvironment.shared.messageExpirationManager.maybeStartExpiration(sentTimestamp, senderPublicKey, expiryMode) } override fun conversationHasOutgoing(userPublicKey: String): Boolean { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt index b79fd14486..51afa5b68a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt @@ -11,6 +11,7 @@ import org.session.libsession.messaging.messages.control.ReadReceipt import org.session.libsession.messaging.sending_receiving.MessageSender.send import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI.nowWithOffset +import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.isReadReceiptsEnabled import org.session.libsession.utilities.associateByNotNull @@ -62,6 +63,18 @@ class MarkReadReceiver : BroadcastReceiver() { sendReadReceipts(context, markedReadMessages) + markedReadMessages + .filter { it.expiryType == ExpiryType.AFTER_READ } + .forEach { info -> + DatabaseComponent.get(context).mmsSmsDatabase().getMessageForTimestamp(info.syncMessageId.timetamp) + ?.takeUnless { it.isExpirationTimerUpdate && it.recipient.isClosedGroupRecipient } + ?.run { + SSKEnvironment.shared.messageExpirationManager.startDisappearAfterRead( + info.syncMessageId.timetamp, + info.syncMessageId.address.serialize() + ) + } + } markedReadMessages.forEach { scheduleDeletion(context, it.expirationInfo) } hashToDisappearAfterReadMessage(context, markedReadMessages)?.let { @@ -77,7 +90,7 @@ class MarkReadReceiver : BroadcastReceiver() { val loki = DatabaseComponent.get(context).lokiMessageDatabase() return markedReadMessages - .filter { it.guessExpiryType() == ExpiryType.AFTER_READ } + .filter { it.expiryType == ExpiryType.AFTER_READ } .associateByNotNull { it.expirationInfo.run { loki.getMessageServerHash(id, isMms) } } .takeIf { it.isNotEmpty() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt index a22470e482..e5acbd16f8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt @@ -49,6 +49,9 @@ class ExpiringMessageManager(context: Context) : MessageExpirationManagerProtoco fun scheduleDeletion(id: Long, mms: Boolean, startedAtTimestamp: Long, expiresInMillis: Long) { Log.d(TAG, "scheduleDeletion() called with: id = $id, mms = $mms, startedAtTimestamp = $startedAtTimestamp, expiresInMillis = $expiresInMillis") + + if (startedAtTimestamp <= 0) return + val expiresAtMillis = startedAtTimestamp + expiresInMillis synchronized(expiringMessageReferences) { expiringMessageReferences += ExpiringMessageReference(id, mms, expiresAtMillis) @@ -164,16 +167,16 @@ class ExpiringMessageManager(context: Context) : MessageExpirationManagerProtoco insertIncomingExpirationTimerMessage(message, expireStartedAt) } - startAnyExpiration(message) + maybeStartExpiration(message) } override fun startAnyExpiration(timestamp: Long, author: String, expireStartedAt: Long) { Log.d(TAG, "startAnyExpiration() called with: timestamp = $timestamp, author = $author, expireStartedAt = $expireStartedAt") - val messageRecord = mmsSmsDatabase.getMessageFor(timestamp, author) ?: throw Exception("no message record!!!") - Log.d(TAG, "startAnyExpiration() $messageRecord") - val mms = messageRecord.isMms() - getDatabase(mms).markExpireStarted(messageRecord.getId(), expireStartedAt) - scheduleDeletion(messageRecord.getId(), mms, expireStartedAt, messageRecord.expiresIn) + + mmsSmsDatabase.getMessageFor(timestamp, author)?.run { + getDatabase(isMms()).markExpireStarted(getId(), expireStartedAt) + scheduleDeletion(getId(), isMms(), expireStartedAt, expiresIn) + } ?: Log.e(TAG, "no message record!!!") } private inner class LoadTask : Runnable { diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt index 894de9de64..6a441b2152 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt @@ -16,6 +16,7 @@ import nl.komponents.kovenant.Promise import nl.komponents.kovenant.functional.bind import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.calls.CallMessageType +import org.session.libsession.messaging.messages.applyExpiryMode import org.session.libsession.messaging.messages.control.CallMessage import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.snode.SnodeAPI @@ -57,8 +58,12 @@ import java.util.ArrayDeque import java.util.UUID import org.thoughtcrime.securesms.webrtc.data.State as CallState -class CallManager(context: Context, audioManager: AudioManagerCompat, private val storage: StorageProtocol): PeerConnection.Observer, - SignalAudioManager.EventListener, CameraEventListener, DataChannel.Observer { +class CallManager( + private val context: Context, + audioManager: AudioManagerCompat, + private val storage: StorageProtocol +): PeerConnection.Observer, + SignalAudioManager.EventListener, CameraEventListener, DataChannel.Observer { sealed class StateEvent { data class AudioEnabled(val isEnabled: Boolean): StateEvent() @@ -293,17 +298,16 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va while (pendingOutgoingIceUpdates.isNotEmpty()) { currentPendings.add(pendingOutgoingIceUpdates.pop()) } - val sdps = currentPendings.map { it.sdp } - val sdpMLineIndexes = currentPendings.map { it.sdpMLineIndex } - val sdpMids = currentPendings.map { it.sdpMid } - - MessageSender.sendNonDurably(CallMessage( - ICE_CANDIDATES, - sdps = sdps, - sdpMLineIndexes = sdpMLineIndexes, - sdpMids = sdpMids, - currentCallId - ), currentRecipient.address, isSyncMessage = currentRecipient.isLocalNumber) + + CallMessage( + ICE_CANDIDATES, + sdps = currentPendings.map(IceCandidate::sdp), + sdpMLineIndexes = currentPendings.map(IceCandidate::sdpMLineIndex), + sdpMids = currentPendings.map(IceCandidate::sdpMid), + currentCallId + ) + .applyExpiryMode() + .also { MessageSender.sendNonDurably(it, currentRecipient.address, isSyncMessage = currentRecipient.isLocalNumber) } } } } diff --git a/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/ExpiryMode.kt b/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/ExpiryMode.kt index dded5d6524..0d9cc9f810 100644 --- a/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/ExpiryMode.kt +++ b/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/ExpiryMode.kt @@ -4,11 +4,13 @@ import kotlin.time.Duration.Companion.seconds sealed class ExpiryMode(val expirySeconds: Long) { object NONE: ExpiryMode(0) - data class Legacy(private val seconds: Long): ExpiryMode(seconds) // after read + data class Legacy(private val seconds: Long): ExpiryMode(seconds) data class AfterSend(private val seconds: Long): ExpiryMode(seconds) data class AfterRead(private val seconds: Long): ExpiryMode(seconds) val duration get() = expirySeconds.seconds val expiryMillis get() = expirySeconds * 1000L + + fun coerceSendToRead(coerce: Boolean = true) = if (coerce && this is AfterSend) AfterRead(expirySeconds) else this } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/Message.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/Message.kt index 6a325d81fb..690cfc509e 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/Message.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/Message.kt @@ -6,6 +6,7 @@ import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.visible.VisibleMessage +import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupUtil import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType @@ -25,6 +26,8 @@ abstract class Message { var expiryMode: ExpiryMode = ExpiryMode.NONE + open val coerceDisappearAfterSendToRead = false + open val defaultTtl: Long = 14 * 24 * 60 * 60 * 1000 open val ttl: Long get() = specifiedTtl ?: defaultTtl open val isSelfSendValid: Boolean = false @@ -51,26 +54,16 @@ abstract class Message { abstract fun toProto(): SignalServiceProtos.Content? fun setGroupContext(dataMessage: SignalServiceProtos.DataMessage.Builder) { - val groupProto = SignalServiceProtos.GroupContext.newBuilder() - val groupID = GroupUtil.doubleEncodeGroupID(recipient!!) - groupProto.id = ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID)) - groupProto.type = SignalServiceProtos.GroupContext.Type.DELIVER - dataMessage.group = groupProto.build() + dataMessage.group = SignalServiceProtos.GroupContext.newBuilder().apply { + id = GroupUtil.doubleEncodeGroupID(recipient!!).let(GroupUtil::getDecodedGroupIDAsData).let(ByteString::copyFrom) + type = SignalServiceProtos.GroupContext.Type.DELIVER + }.build() } - fun SignalServiceProtos.Content.Builder.setExpirationConfigurationIfNeeded( - threadId: Long?, - coerceDisappearAfterSendToRead: Boolean = false - ): SignalServiceProtos.Content.Builder { - val config = threadId?.let(MessagingModuleConfiguration.shared.storage::getExpirationConfiguration) - ?: run { - expirationTimer = 0 - return this - } - expirationTimer = config.expiryMode.expirySeconds.toInt() - lastDisappearingMessageChangeTimestamp = config.updatedTimestampMs - expirationType = when (config.expiryMode) { - is ExpiryMode.AfterSend -> if (coerceDisappearAfterSendToRead) ExpirationType.DELETE_AFTER_READ else ExpirationType.DELETE_AFTER_SEND + fun SignalServiceProtos.Content.Builder.applyExpiryMode(): SignalServiceProtos.Content.Builder { + expirationTimer = expiryMode.expirySeconds.toInt() + expirationType = when (expiryMode) { + is ExpiryMode.AfterSend -> ExpirationType.DELETE_AFTER_SEND is ExpiryMode.AfterRead -> ExpirationType.DELETE_AFTER_READ else -> ExpirationType.UNKNOWN } @@ -79,13 +72,36 @@ abstract class Message { } inline fun M.copyExpiration(proto: SignalServiceProtos.Content): M { - val duration: Int = (if (proto.hasExpirationTimer()) proto.expirationTimer else if (proto.hasDataMessage()) proto.dataMessage?.expireTimer else null) ?: return this - - expiryMode = when (proto.expirationType.takeIf { duration > 0 }) { - ExpirationType.DELETE_AFTER_SEND -> ExpiryMode.AfterSend(duration.toLong()) - ExpirationType.DELETE_AFTER_READ -> ExpiryMode.AfterRead(duration.toLong()) - else -> ExpiryMode.NONE + (proto.takeIf { it.hasExpirationTimer() }?.expirationTimer ?: proto.dataMessage?.expireTimer)?.let { duration -> + expiryMode = when (proto.expirationType.takeIf { duration > 0 }) { + ExpirationType.DELETE_AFTER_SEND -> ExpiryMode.AfterSend(duration.toLong()) + ExpirationType.DELETE_AFTER_READ -> ExpiryMode.AfterRead(duration.toLong()) + else -> ExpiryMode.NONE + } } return this } +fun SignalServiceProtos.Content.expiryMode(): ExpiryMode = + (takeIf { it.hasExpirationTimer() }?.expirationTimer ?: dataMessage?.expireTimer)?.let { duration -> + when (expirationType.takeIf { duration > 0 }) { + ExpirationType.DELETE_AFTER_SEND -> ExpiryMode.AfterSend(duration.toLong()) + ExpirationType.DELETE_AFTER_READ -> ExpiryMode.AfterRead(duration.toLong()) + else -> ExpiryMode.NONE + } + } ?: ExpiryMode.NONE + +/** + * Apply ExpiryMode from the current setting. + */ +inline fun M.applyExpiryMode(): M { + val address = Address.fromSerialized(sender ?: return this) + MessagingModuleConfiguration.shared.storage.getThreadId(address)?.let(::applyExpiryMode) + return this +} + +inline fun M.applyExpiryMode(thread: Long): M { + val storage = MessagingModuleConfiguration.shared.storage + expiryMode = storage.getExpirationConfiguration(thread)?.expiryMode?.coerceSendToRead(coerceDisappearAfterSendToRead) ?: ExpiryMode.NONE + return this +} diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/CallMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/CallMessage.kt index 4ef15367d6..4ae99e7605 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/CallMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/CallMessage.kt @@ -1,5 +1,6 @@ package org.session.libsession.messaging.messages.control +import org.session.libsession.messaging.messages.applyExpiryMode import org.session.libsession.messaging.messages.copyExpiration import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.* @@ -13,6 +14,7 @@ class CallMessage(): ControlMessage() { var sdpMids: List = listOf() var callId: UUID? = null + override val coerceDisappearAfterSendToRead = true override val isSelfSendValid: Boolean get() = type in arrayOf(ANSWER, END_CALL) override val defaultTtl: Long = 300000L // 5m @@ -40,21 +42,21 @@ class CallMessage(): ControlMessage() { listOf(), listOf(), callId - ) + ).applyExpiryMode() fun preOffer(callId: UUID) = CallMessage(PRE_OFFER, listOf(), listOf(), listOf(), callId - ) + ).applyExpiryMode() fun offer(sdp: String, callId: UUID) = CallMessage(OFFER, listOf(sdp), listOf(), listOf(), callId - ) + ).applyExpiryMode() fun endCall(callId: UUID) = CallMessage(END_CALL, emptyList(), emptyList(), emptyList(), callId) @@ -83,9 +85,8 @@ class CallMessage(): ControlMessage() { .addAllSdpMids(sdpMids) .setUuid(callId!!.toString()) - val content = SignalServiceProtos.Content.newBuilder() - content.setExpirationConfigurationIfNeeded(threadID, true) - return content + return SignalServiceProtos.Content.newBuilder() + .applyExpiryMode() .setCallMessage(callMessage) .build() } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt index 41340d6e88..c88fd71510 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt @@ -178,7 +178,7 @@ class ClosedGroupControlMessage() : ControlMessage() { contentProto.dataMessage = dataMessageProto.build() // Expiration timer val threadId = groupID?.let { MessagingModuleConfiguration.shared.storage.getOrCreateThreadIdFor(Address.fromSerialized(it)) } - contentProto.setExpirationConfigurationIfNeeded(threadId) + contentProto.applyExpiryMode() return contentProto.build() } catch (e: Exception) { Log.w(TAG, "Couldn't construct closed group control message proto from: $this.") diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt index 5a3db978ce..7544ff9c82 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt @@ -20,10 +20,10 @@ class ConfigurationMessage(var closedGroups: List, var openGroups: override val isSelfSendValid: Boolean = true - class ClosedGroup(var publicKey: String, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List, var admins: List, var expirationTimer: Int) { + class ClosedGroup(var publicKey: String, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List, var admins: List) { val isValid: Boolean get() = members.isNotEmpty() && admins.isNotEmpty() - internal constructor() : this("", "", null, listOf(), listOf(), 0) + internal constructor() : this("", "", null, listOf(), listOf()) override fun toString(): String { return name @@ -40,8 +40,7 @@ class ConfigurationMessage(var closedGroups: List, var openGroups: DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray())) val members = proto.membersList.map { it.toByteArray().toHexString() } val admins = proto.adminsList.map { it.toByteArray().toHexString() } - val expirationTimer = proto.expirationTimer - return ClosedGroup(publicKey, name, encryptionKeyPair, members, admins, expirationTimer) + return ClosedGroup(publicKey, name, encryptionKeyPair, members, admins) } } @@ -55,7 +54,6 @@ class ConfigurationMessage(var closedGroups: List, var openGroups: result.encryptionKeyPair = encryptionKeyPairAsProto.build() result.addAllMembers(members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) result.addAllAdmins(admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) - result.expirationTimer = expirationTimer return result.build() } } @@ -128,15 +126,12 @@ class ConfigurationMessage(var closedGroups: List, var openGroups: if (!group.members.contains(Address.fromSerialized(storage.getUserPublicKey()!!))) continue val groupPublicKey = GroupUtil.doubleDecodeGroupID(group.encodedId).toHexString() val encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: continue - val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(group.encodedId)) - val expiryConfig = storage.getExpirationConfiguration(threadID) val closedGroup = ClosedGroup( groupPublicKey, group.title, encryptionKeyPair, group.members.map { it.serialize() }, - group.admins.map { it.serialize() }, - expiryConfig?.expiryMode?.expirySeconds?.toInt() ?: 0 + group.admins.map { it.serialize() } ) closedGroups.add(closedGroup) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt index bfe3c92cba..7ac009b6a6 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt @@ -7,6 +7,8 @@ import org.session.libsignal.utilities.Log class DataExtractionNotification() : ControlMessage() { var kind: Kind? = null + override val coerceDisappearAfterSendToRead = true + sealed class Kind { class Screenshot() : Kind() class MediaSaved(val timestamp: Long) : Kind() @@ -64,10 +66,10 @@ class DataExtractionNotification() : ControlMessage() { dataExtractionNotification.timestamp = kind.timestamp } } - val contentProto = SignalServiceProtos.Content.newBuilder() - contentProto.dataExtractionNotification = dataExtractionNotification.build() - contentProto.setExpirationConfigurationIfNeeded(threadID, true) - return contentProto.build() + return SignalServiceProtos.Content.newBuilder() + .setDataExtractionNotification(dataExtractionNotification.build()) + .applyExpiryMode() + .build() } catch (e: Exception) { Log.w(TAG, "Couldn't construct data extraction notification proto from: $this") return null diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt index ec50d409c4..e8732db889 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt @@ -1,59 +1,53 @@ package org.session.libsession.messaging.messages.control -import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.copyExpiration import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsignal.protos.SignalServiceProtos +import org.session.libsignal.protos.SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE import org.session.libsignal.utilities.Log /** In the case of a sync message, the public key of the person the message was targeted at. * * **Note:** `nil` if this isn't a sync message. */ -data class ExpirationTimerUpdate(var syncTarget: String? = null) : ControlMessage() { +data class ExpirationTimerUpdate(var syncTarget: String? = null, val isGroup: Boolean = false) : ControlMessage() { override val isSelfSendValid: Boolean = true companion object { const val TAG = "ExpirationTimerUpdate" + private val storage = MessagingModuleConfiguration.shared.storage - fun fromProto(proto: SignalServiceProtos.Content): ExpirationTimerUpdate? { - val dataMessageProto = if (proto.hasDataMessage()) proto.dataMessage else return null - val isExpirationTimerUpdate = dataMessageProto.flags.and( - SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE - ) != 0 - if (!isExpirationTimerUpdate) return null - - return ExpirationTimerUpdate(dataMessageProto.syncTarget) - .copyExpiration(proto) - } + fun fromProto(proto: SignalServiceProtos.Content): ExpirationTimerUpdate? = + proto.dataMessage?.takeIf { it.flags and EXPIRATION_TIMER_UPDATE_VALUE != 0 }?.run { + ExpirationTimerUpdate(syncTarget, hasGroup()).copyExpiration(proto) + } } override fun toProto(): SignalServiceProtos.Content? { - val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder() - dataMessageProto.flags = SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE - dataMessageProto.expireTimer = expiryMode.expirySeconds.toInt() - // Sync target - if (syncTarget != null) { - dataMessageProto.syncTarget = syncTarget + val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder().apply { + flags = EXPIRATION_TIMER_UPDATE_VALUE + expireTimer = expiryMode.expirySeconds.toInt() } + // Sync target + syncTarget?.let { dataMessageProto.syncTarget = it } // Group context - if (MessagingModuleConfiguration.shared.storage.isClosedGroup(recipient!!)) { + if (storage.isClosedGroup(recipient!!)) { try { setGroupContext(dataMessageProto) } catch(e: Exception) { - Log.w(VisibleMessage.TAG, "Couldn't construct visible message proto from: $this") + Log.w(TAG, "Couldn't construct visible message proto from: $this", e) return null } } return try { - SignalServiceProtos.Content.newBuilder().apply { - dataMessage = dataMessageProto.build() - setExpirationConfigurationIfNeeded(threadID) - }.build() + SignalServiceProtos.Content.newBuilder() + .setDataMessage(dataMessageProto) + .applyExpiryMode() + .build() } catch (e: Exception) { - Log.w(TAG, "Couldn't construct expiration timer update proto from: $this") + Log.w(TAG, "Couldn't construct expiration timer update proto from: $this", e) null } } -} \ No newline at end of file +} diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt index 57e6cff29d..df30b9cb74 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt @@ -20,7 +20,7 @@ class MessageRequestResponse(val isApproved: Boolean, var profile: Profile? = nu profile?.profileKey?.let { messageRequestResponseProto.profileKey = ByteString.copyFrom(it) } return try { SignalServiceProtos.Content.newBuilder() - .setExpirationConfigurationIfNeeded(threadID) + .applyExpiryMode() .setMessageRequestResponse(messageRequestResponseProto.build()) .build() } catch (e: Exception) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ReadReceipt.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ReadReceipt.kt index 9cfb3bbc52..e635c3c0cf 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ReadReceipt.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ReadReceipt.kt @@ -37,14 +37,15 @@ class ReadReceipt() : ControlMessage() { Log.w(TAG, "Couldn't construct read receipt proto from: $this") return null } - val receiptProto = SignalServiceProtos.ReceiptMessage.newBuilder() - receiptProto.type = SignalServiceProtos.ReceiptMessage.Type.READ - receiptProto.addAllTimestamp(timestamps.asIterable()) - val contentProto = SignalServiceProtos.Content.newBuilder() + return try { - contentProto.receiptMessage = receiptProto.build() - contentProto.setExpirationConfigurationIfNeeded(threadID) - contentProto.build() + SignalServiceProtos.Content.newBuilder() + .setReceiptMessage( + SignalServiceProtos.ReceiptMessage.newBuilder() + .setType(SignalServiceProtos.ReceiptMessage.Type.READ) + .addAllTimestamp(timestamps.asIterable()).build() + ).applyExpiryMode() + .build() } catch (e: Exception) { Log.w(TAG, "Couldn't construct read receipt proto from: $this") null diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/TypingIndicator.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/TypingIndicator.kt index bfb1b7554d..343bb63544 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/TypingIndicator.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/TypingIndicator.kt @@ -56,14 +56,11 @@ class TypingIndicator() : ControlMessage() { Log.w(TAG, "Couldn't construct typing indicator proto from: $this") return null } - val typingIndicatorProto = SignalServiceProtos.TypingMessage.newBuilder() - typingIndicatorProto.timestamp = timestamp - typingIndicatorProto.action = kind.toProto() - val contentProto = SignalServiceProtos.Content.newBuilder() return try { - contentProto.typingMessage = typingIndicatorProto.build() - contentProto.setExpirationConfigurationIfNeeded(threadID) - contentProto.build() + SignalServiceProtos.Content.newBuilder() + .setTypingMessage(SignalServiceProtos.TypingMessage.newBuilder().setTimestamp(timestamp).setAction(kind.toProto()).build()) + .applyExpiryMode() + .build() } catch (e: Exception) { Log.w(TAG, "Couldn't construct typing indicator proto from: $this") null diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/UnsendRequest.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/UnsendRequest.kt index 301503cef7..282a1ac5c6 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/UnsendRequest.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/UnsendRequest.kt @@ -41,14 +41,11 @@ class UnsendRequest(): ControlMessage() { Log.w(TAG, "Couldn't construct unsend request proto from: $this") return null } - val unsendRequestProto = SignalServiceProtos.UnsendRequest.newBuilder() - unsendRequestProto.timestamp = timestamp - unsendRequestProto.author = author - val contentProto = SignalServiceProtos.Content.newBuilder() return try { - contentProto.unsendRequest = unsendRequestProto.build() - contentProto.setExpirationConfigurationIfNeeded(threadID) - contentProto.build() + SignalServiceProtos.Content.newBuilder() + .setUnsendRequest(SignalServiceProtos.UnsendRequest.newBuilder().setTimestamp(timestamp).setAuthor(author).build()) + .applyExpiryMode() + .build() } catch (e: Exception) { Log.w(TAG, "Couldn't construct unsend request proto from: $this") null diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt index 04ceeb66ef..70d5cafec4 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt @@ -44,52 +44,26 @@ data class VisibleMessage( companion object { const val TAG = "VisibleMessage" - fun fromProto(proto: SignalServiceProtos.Content): VisibleMessage? { - val dataMessage = proto.dataMessage ?: return null - val result = VisibleMessage() - if (dataMessage.hasSyncTarget()) { result.syncTarget = dataMessage.syncTarget } - result.text = dataMessage.body - // Attachments are handled in MessageReceiver - val quoteProto = if (dataMessage.hasQuote()) dataMessage.quote else null - if (quoteProto != null) { - val quote = Quote.fromProto(quoteProto) - result.quote = quote - } - val linkPreviewProto = dataMessage.previewList.firstOrNull() - if (linkPreviewProto != null) { - val linkPreview = LinkPreview.fromProto(linkPreviewProto) - result.linkPreview = linkPreview - } - val openGroupInvitationProto = if (dataMessage.hasOpenGroupInvitation()) dataMessage.openGroupInvitation else null - if (openGroupInvitationProto != null) { - val openGroupInvitation = OpenGroupInvitation.fromProto(openGroupInvitationProto) - result.openGroupInvitation = openGroupInvitation - } - // TODO Contact - val profile = Profile.fromProto(dataMessage) - if (profile != null) { result.profile = profile } - val reactionProto = if (dataMessage.hasReaction()) dataMessage.reaction else null - if (reactionProto != null) { - val reaction = Reaction.fromProto(reactionProto) - result.reaction = reaction - } - - result.blocksMessageRequests = with (dataMessage) { hasBlocksCommunityMessageRequests() && blocksCommunityMessageRequests } - - return result.copyExpiration(proto) + fun fromProto(proto: SignalServiceProtos.Content): VisibleMessage? = + proto.dataMessage?.let { VisibleMessage().apply { + if (it.hasSyncTarget()) syncTarget = it.syncTarget + text = it.body + // Attachments are handled in MessageReceiver + if (it.hasQuote()) quote = Quote.fromProto(it.quote) + linkPreview = it.previewList.firstOrNull()?.let(LinkPreview::fromProto) + if (it.hasOpenGroupInvitation()) openGroupInvitation = it.openGroupInvitation?.let(OpenGroupInvitation::fromProto) + // TODO Contact + profile = Profile.fromProto(it) + if (it.hasReaction()) reaction = it.reaction?.let(Reaction::fromProto) + blocksMessageRequests = it.hasBlocksCommunityMessageRequests() && it.blocksCommunityMessageRequests + }.copyExpiration(proto) } } override fun toProto(): SignalServiceProtos.Content? { val proto = SignalServiceProtos.Content.newBuilder() - val dataMessage: SignalServiceProtos.DataMessage.Builder // Profile - val profileProto = profile?.toProto() - dataMessage = if (profileProto != null) { - profileProto.toBuilder() - } else { - SignalServiceProtos.DataMessage.newBuilder() - } + val dataMessage = profile?.toProto()?.toBuilder() ?: SignalServiceProtos.DataMessage.newBuilder() // Text if (text != null) { dataMessage.body = text } // Quote @@ -124,7 +98,7 @@ data class VisibleMessage( dataMessage.addAllAttachments(pointers) // TODO: Contact // Expiration timer - proto.setExpirationConfigurationIfNeeded(threadID) + proto.applyExpiryMode() // Group context val storage = MessagingModuleConfiguration.shared.storage if (storage.isClosedGroup(recipient!!)) { @@ -163,4 +137,4 @@ data class VisibleMessage( fun isMediaMessage(): Boolean { return attachmentIDs.isNotEmpty() || quote != null || linkPreview != null || reaction != null } -} \ No newline at end of file +} diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt index 1571bb22c9..8d9d69fb82 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt @@ -145,9 +145,7 @@ object MessageReceiver { MessageRequestResponse.fromProto(proto) ?: CallMessage.fromProto(proto) ?: SharedConfigurationMessage.fromProto(proto) ?: - VisibleMessage.fromProto(proto) ?: run { - throw Error.UnknownMessage - } + VisibleMessage.fromProto(proto) ?: throw Error.UnknownMessage val isUserBlindedSender = sender == openGroupPublicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId(IdPrefix.BLINDED, it.publicKey.asBytes).hexString } val isUserSender = sender == userPublicKey diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index 424f52c2d7..d68c8f5e37 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -9,6 +9,7 @@ import org.session.libsession.messaging.jobs.MessageSendJob import org.session.libsession.messaging.jobs.NotifyPNServerJob import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.Message +import org.session.libsession.messaging.messages.applyExpiryMode import org.session.libsession.messaging.messages.control.CallMessage import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage import org.session.libsession.messaging.messages.control.ConfigurationMessage @@ -421,12 +422,7 @@ object MessageSender { storage.markUnidentified(timestamp, userPublicKey) // Start the disappearing messages timer if needed Log.d("MessageSender", "Start the disappearing messages timer if needed message.recipient = ${message.recipient}, userPublicKey = $userPublicKey, isSyncMessage = $isSyncMessage") - message.threadID?.let(storage::getExpirationConfiguration)?.expiryMode?.takeIf { it.expirySeconds > 0 }?.let { mode -> - if (message.recipient == userPublicKey || !isSyncMessage) { - val expireStartedAt = if (mode is ExpiryMode.AfterRead) timestamp + 1 else timestamp - SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(timestamp, userPublicKey, expireStartedAt) - } - } + SSKEnvironment.shared.messageExpirationManager.maybeStartExpiration(message, startDisappearAfterRead = true) } ?: run { storage.updateReactionIfNeeded(message, message.sender?:userPublicKey, openGroupSentTimestamp) } @@ -475,6 +471,7 @@ object MessageSender { @JvmStatic fun send(message: Message, address: Address) { + message.applyExpiryMode() val threadID = MessagingModuleConfiguration.shared.storage.getThreadId(address) message.threadID = threadID val destination = Destination.from(address) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index c288278b99..313ad161e8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -124,7 +124,6 @@ private fun MessageReceiver.handleReadReceipt(message: ReadReceipt) { private fun MessageReceiver.handleCallMessage(message: CallMessage) { // TODO: refactor this out to persistence, just to help debug the flow and send/receive in synchronous testing WebRtcUtils.SIGNAL_QUEUE.trySend(message) - SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(message, coerceToDisappearAfterRead = true) } private fun MessageReceiver.handleTypingIndicator(message: TypingIndicator) { @@ -192,7 +191,6 @@ private fun MessageReceiver.handleDataExtractionNotification(message: DataExtrac else -> return } storage.insertDataExtractionNotificationMessage(senderPublicKey, notification, message.sentTimestamp!!) - SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(message, coerceToDisappearAfterRead = true) } private fun handleConfigurationMessage(message: ConfigurationMessage) { @@ -221,7 +219,7 @@ private fun handleConfigurationMessage(message: ConfigurationMessage) { } else { // only handle new closed group if it's first time sync handleNewClosedGroup(message.sender!!, message.sentTimestamp!!, closedGroup.publicKey, closedGroup.name, - closedGroup.encryptionKeyPair!!, closedGroup.members, closedGroup.admins, message.sentTimestamp!!, closedGroup.expirationTimer) + closedGroup.encryptionKeyPair!!, closedGroup.members, closedGroup.admins, message.sentTimestamp!!) } } val allV2OpenGroups = storage.getAllOpenGroups().map { it.value.joinURL } @@ -431,7 +429,7 @@ fun MessageReceiver.handleVisibleMessage( val isSms = !message.isMediaMessage() && attachments.isEmpty() storage.setOpenGroupServerMessageID(messageID, it, threadID, isSms) } - SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(message) + SSKEnvironment.shared.messageExpirationManager.maybeStartExpiration(message) return messageID } return null @@ -550,10 +548,10 @@ private fun MessageReceiver.handleNewClosedGroup(message: ClosedGroupControlMess val members = kind.members.map { it.toByteArray().toHexString() } val admins = kind.admins.map { it.toByteArray().toHexString() } val expirationTimer = kind.expirationTimer - handleNewClosedGroup(message.sender!!, message.sentTimestamp!!, groupPublicKey, kind.name, kind.encryptionKeyPair!!, members, admins, message.sentTimestamp!!, expirationTimer) + handleNewClosedGroup(message.sender!!, message.sentTimestamp!!, groupPublicKey, kind.name, kind.encryptionKeyPair!!, members, admins, message.sentTimestamp!!) } -private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPublicKey: String, name: String, encryptionKeyPair: ECKeyPair, members: List, admins: List, formationTimestamp: Long, expireTimer: Int) { +private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPublicKey: String, name: String, encryptionKeyPair: ECKeyPair, members: List, admins: List, formationTimestamp: Long) { val context = MessagingModuleConfiguration.shared.context val storage = MessagingModuleConfiguration.shared.storage val userPublicKey = storage.getUserPublicKey()!! diff --git a/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt b/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt index 3bba820fc0..3e056fd005 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt @@ -3,11 +3,12 @@ package org.session.libsession.utilities import android.content.Context import android.util.Log import network.loki.messenger.libsession_util.util.ExpiryMode +import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier -import org.session.libsession.snode.SnodeAPI +import org.session.libsession.snode.SnodeAPI.nowWithOffset import org.session.libsession.utilities.recipients.Recipient class SSKEnvironment( @@ -43,18 +44,41 @@ class SSKEnvironment( interface MessageExpirationManagerProtocol { fun insertExpirationTimerMessage(message: ExpirationTimerUpdate) fun startAnyExpiration(timestamp: Long, author: String, expireStartedAt: Long) - fun startAnyExpiration(message: Message, coerceToDisappearAfterRead: Boolean = false) { - Log.d("MessageExpirationManagerProtocol", "startAnyExpiration() called with: message = $message, coerceToDisappearAfterRead = $coerceToDisappearAfterRead") - val timestamp = message.sentTimestamp ?: return + fun maybeStartExpiration(message: Message, startDisappearAfterRead: Boolean = false) { + Log.d("MessageExpirationManagerProtocol", "maybeStartExpiration() called with: message = $message, startDisappearAfterRead = $startDisappearAfterRead") + + if (message is ExpirationTimerUpdate && message.isGroup) return + + maybeStartExpiration( + message.sentTimestamp ?: return, + message.sender ?: return, + message.expiryMode, + startDisappearAfterRead || message.isSenderSelf + ) + } + + fun startDisappearAfterRead(timestamp: Long, sender: String) { + Log.d("MessageExpirationManagerProtocol", "startDisappearAfterRead() called with: timestamp = $timestamp, sender = $sender") + startAnyExpiration( - timestamp = timestamp, - author = message.sender ?: return, - expireStartedAt = if (message.expiryMode is ExpiryMode.AfterRead || coerceToDisappearAfterRead && message.expiryMode.expiryMillis > 0) SnodeAPI.nowWithOffset.coerceAtLeast(timestamp + 1) - else if (message.expiryMode is ExpiryMode.AfterSend) timestamp - else return + timestamp, + sender, + expireStartedAt = nowWithOffset.coerceAtLeast(timestamp + 1) ) } + + fun maybeStartExpiration(timestamp: Long, sender: String, mode: ExpiryMode, startDisappearAfterRead: Boolean = false) { + Log.d("MessageExpirationManagerProtocol", "maybeStartExpiration() called with: timestamp = $timestamp, sender = $sender, mode = $mode, startDisappearAfterRead = $startDisappearAfterRead") + + val expireStartedAt = when (mode) { + is ExpiryMode.AfterSend -> timestamp + is ExpiryMode.AfterRead -> if (startDisappearAfterRead) nowWithOffset.coerceAtLeast(timestamp + 1) else return + else -> return + } + + startAnyExpiration(timestamp, sender, expireStartedAt) + } } companion object {