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 new file mode 100644 index 0000000000..da0786906d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt @@ -0,0 +1,38 @@ +package org.thoughtcrime.securesms.conversation.disappearingmessages + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import network.loki.messenger.libsession_util.util.ExpiryMode +import org.session.libsession.messaging.MessagingModuleConfiguration +import org.session.libsession.messaging.messages.ExpirationConfiguration +import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate +import org.session.libsession.messaging.sending_receiving.MessageSender +import org.session.libsession.snode.SnodeAPI +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.SSKEnvironment +import org.session.libsession.utilities.SSKEnvironment.MessageExpirationManagerProtocol +import org.session.libsession.utilities.TextSecurePreferences +import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities +import javax.inject.Inject + +class DisappearingMessages @Inject constructor( + @ApplicationContext private val context: Context, + private val textSecurePreferences: TextSecurePreferences, + private val messageExpirationManager: MessageExpirationManagerProtocol, +) { + fun set(threadId: Long, address: Address, mode: ExpiryMode) { + val expiryChangeTimestampMs = SnodeAPI.nowWithOffset + MessagingModuleConfiguration.shared.storage.setExpirationConfiguration(ExpirationConfiguration(threadId, mode, expiryChangeTimestampMs)) + + val message = ExpirationTimerUpdate(mode).apply { + sender = textSecurePreferences.getLocalNumber() + isSenderSelf = true + recipient = address.serialize() + sentTimestamp = expiryChangeTimestampMs + } + + messageExpirationManager.setExpirationTimer(message) + MessageSender.send(message, address) + ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context) + } +} 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 fb9ed095cc..fce6b96613 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 @@ -99,7 +99,7 @@ class DisappearingMessagesViewModel( recipient = address.serialize() sentTimestamp = expiryChangeTimestampMs } - messageExpirationManager.setExpirationTimer(message, mode) + messageExpirationManager.setExpirationTimer(message) MessageSender.send(message, address) ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(application) 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 5d49f1a78d..473c1c497b 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 @@ -8,16 +8,26 @@ import androidx.core.content.res.ResourcesCompat import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView +import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import network.loki.messenger.databinding.ViewControlMessageBinding +import network.loki.messenger.libsession_util.util.ExpiryMode +import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.utilities.ExpirationUtil.getExpirationDisplayValue import org.session.libsession.utilities.getExpirationTypeDisplayValue +import org.thoughtcrime.securesms.conversation.disappearingmessages.DisappearingMessages import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.showSessionDialog +import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds +private val MessageRecord.expiryMode get() = if (expiresIn <= 0) ExpiryMode.NONE + else if (expireStarted == timestamp) ExpiryMode.AfterSend(expiresIn / 1000) + else ExpiryMode.AfterRead(expiresIn / 1000) + +@AndroidEntryPoint class ControlMessageView : LinearLayout { private lateinit var binding: ViewControlMessageBinding @@ -26,6 +36,8 @@ class ControlMessageView : LinearLayout { constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() } + @Inject lateinit var disappearingMessages: DisappearingMessages + private fun initialize() { binding = ViewControlMessageBinding.inflate(LayoutInflater.from(context), this, true) layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT) @@ -42,24 +54,31 @@ class ControlMessageView : LinearLayout { message.isExpirationTimerUpdate -> { binding.apply { expirationTimerView.isVisible = true + + expirationTimerView.setExpirationTime(message.expireStarted, message.expiresIn) - followSetting.isVisible = ExpirationConfiguration.isNewConfigEnabled && !message.isOutgoing + + followSetting.isVisible = ExpirationConfiguration.isNewConfigEnabled + && !message.isOutgoing + && message.expiryMode != (MessagingModuleConfiguration.shared.storage.getExpirationConfiguration(message.threadId)?.expiryMode ?: ExpiryMode.NONE) + followSetting.setOnClickListener { context.showSessionDialog { - val isOff = message.expiresIn == 0L title(R.string.dialog_disappearing_messages_follow_setting_title) - if (isOff) { + if (message.expiresIn == 0L) { text(R.string.dialog_disappearing_messages_follow_setting_off_body) } else { text( context.getString( R.string.dialog_disappearing_messages_follow_setting_on_body, getExpirationDisplayValue(context, message.expiresIn.milliseconds), - context.getExpirationTypeDisplayValue(message.expireStarted == message.timestamp) + context.getExpirationTypeDisplayValue(message.isNotDisappearAfterRead) ) ) } - destructiveButton(if (isOff) R.string.dialog_disappearing_messages_follow_setting_confirm else R.string.dialog_disappearing_messages_follow_setting_set) { } + destructiveButton(if (message.expiresIn == 0L) R.string.dialog_disappearing_messages_follow_setting_confirm else R.string.dialog_disappearing_messages_follow_setting_set) { + disappearingMessages.set(message.threadId, message.recipient.address, message.expiryMode) + } cancelButton() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index d3925197f9..339e9bf536 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -8,6 +8,7 @@ import android.graphics.drawable.ColorDrawable import android.os.Handler import android.os.Looper import android.util.AttributeSet +import android.util.Log import android.view.Gravity import android.view.HapticFeedbackConstants import android.view.MotionEvent @@ -61,6 +62,8 @@ import kotlin.math.min import kotlin.math.roundToInt import kotlin.math.sqrt +private const val TAG = "VisibleMessageView" + @AndroidEntryPoint class VisibleMessageView : LinearLayout { @@ -349,6 +352,8 @@ class VisibleMessageView : LinearLayout { } private fun updateExpirationTimer(message: MessageRecord) { + Log.d(TAG, "updateExpirationTimer() called with: message = $message") + if (!message.isOutgoing) binding.messageStatusTextView.bringToFront() val expireStarted = message.expireStarted.takeIf { it > 0 } ?: SnodeAPI.nowWithOffset diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index fbc36337bb..3b61bcd3c6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -54,6 +54,10 @@ public abstract class MessageRecord extends DisplayRecord { private final List reactions; private final boolean hasMention; + public final boolean isNotDisappearAfterRead() { + return expireStarted == getTimestamp(); + } + public abstract boolean isMms(); public abstract boolean isMmsNotification(); 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 c9aaf27fcf..21cb6f334b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.kt @@ -58,6 +58,8 @@ class MarkReadReceiver : BroadcastReceiver() { ) { if (markedReadMessages.isEmpty()) return + Log.d(TAG, "process() called with: context = $context, markedReadMessages = $markedReadMessages") + sendReadReceipts(context, markedReadMessages) markedReadMessages.forEach { scheduleDeletion(context, it.expirationInfo) } @@ -119,6 +121,8 @@ class MarkReadReceiver : BroadcastReceiver() { context: Context, hashToMessage: Map ) { + Log.d(TAG, "fetchUpdatedExpiriesAndScheduleDeletion() called with: context = $context, hashToMessage = $hashToMessage") + @Suppress("UNCHECKED_CAST") val expiries = SnodeAPI.getExpiries(hashToMessage.keys.toList(), TextSecurePreferences.getLocalNumber(context)!!).get()["expiries"] as Map hashToMessage.forEach { (hash, info) -> expiries[hash]?.let { scheduleDeletion(context, info.expirationInfo, it - info.expirationInfo.expireStarted) } } @@ -129,6 +133,8 @@ class MarkReadReceiver : BroadcastReceiver() { expirationInfo: ExpirationInfo, expiresIn: Long = expirationInfo.expiresIn ) { + Log.d(TAG, "scheduleDeletion() called with: context = $context, expirationInfo = $expirationInfo, expiresIn = $expiresIn") + if (expiresIn <= 0 || expirationInfo.expireStarted > 0) return val now = SnodeAPI.nowWithOffset 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 a609c34ad1..b97b279cb4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.kt @@ -62,7 +62,8 @@ class ExpiringMessageManager(context: Context) : MessageExpirationManagerProtoco synchronized(expiringMessageReferences) { (expiringMessageReferences as Object).notifyAll() } } - override fun setExpirationTimer(message: ExpirationTimerUpdate, expiryMode: ExpiryMode?) { + override fun setExpirationTimer(message: ExpirationTimerUpdate) { + val expiryMode: ExpiryMode = message.expiryMode Log.d(TAG, "setExpirationTimer() called with: message = $message, expiryMode = $expiryMode") val userPublicKey = getLocalNumber(context) 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 4c534802e9..e87546e42c 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 @@ -155,6 +155,7 @@ fun MessageReceiver.cancelTypingIndicatorsIfNeeded(senderPublicKey: String) { private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimerUpdate) { if (ExpirationConfiguration.isNewConfigEnabled) return + val module = MessagingModuleConfiguration.shared try { val threadId = fromSerialized(message.groupPublicKey?.let(::doubleEncodeGroupID) ?: message.sender!!) @@ -170,7 +171,7 @@ private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimer } catch (e: Exception) { Log.e("Loki", "Failed to update expiration configuration.") } - SSKEnvironment.shared.messageExpirationManager.setExpirationTimer(message, message.expiryMode) + SSKEnvironment.shared.messageExpirationManager.setExpirationTimer(message) } private fun MessageReceiver.handleDataExtractionNotification(message: DataExtractionNotification) { @@ -298,24 +299,25 @@ fun MessageReceiver.updateExpiryIfNeeded( val lastDisappearingMessageChangeTimestamp = proto.lastDisappearingMessageChangeTimestamp + // don't update any values for open groups + if (recipient.isOpenGroupRecipient && type != null) throw MessageReceiver.Error.InvalidMessage + + val incoming1on1 = recipient.isContactRecipient && !message.isSenderSelf + val remoteConfig = ExpirationConfiguration( threadID, expiryMode, lastDisappearingMessageChangeTimestamp ) - // don't update any values for open groups - if (recipient.isOpenGroupRecipient && type != null) throw MessageReceiver.Error.InvalidMessage - - remoteConfig.takeIf { + remoteConfig.takeUnless { incoming1on1 }?.takeIf { localConfig == null || it.updatedTimestampMs > localConfig.updatedTimestampMs - || !isNewConfigEnabled && !proto.hasLastDisappearingMessageChangeTimestamp() } - ?.let(storage::setExpirationConfiguration) - + || !isNewConfigEnabled && !proto.hasLastDisappearingMessageChangeTimestamp() + }?.let(storage::setExpirationConfiguration) if (message is ExpirationTimerUpdate) { - SSKEnvironment.shared.messageExpirationManager.setExpirationTimer(message, expiryMode) + SSKEnvironment.shared.messageExpirationManager.setExpirationTimer(message) } } 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 5a6af4f7e9..378413b6b1 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt @@ -38,7 +38,7 @@ class SSKEnvironment( } interface MessageExpirationManagerProtocol { - fun setExpirationTimer(message: ExpirationTimerUpdate, expiryType: ExpiryMode?) + fun setExpirationTimer(message: ExpirationTimerUpdate) fun startAnyExpiration(timestamp: Long, author: String, expireStartedAt: Long) }