Add support for versioned expiration timers.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
This commit is contained in:
parent
4152294b57
commit
1f196f74ff
43 changed files with 392 additions and 139 deletions
|
@ -77,7 +77,7 @@ class EditMessageSyncProcessorTest {
|
||||||
.build()
|
.build()
|
||||||
).build()
|
).build()
|
||||||
).build()
|
).build()
|
||||||
SignalDatabase.recipients.setExpireMessages(toRecipient.id, content.dataMessage?.expireTimer ?: 0)
|
SignalDatabase.recipients.setExpireMessages(toRecipient.id, content.dataMessage?.expireTimer ?: 0, content.dataMessage?.expireTimerVersion ?: 1)
|
||||||
val syncTextMessage = TestMessage(
|
val syncTextMessage = TestMessage(
|
||||||
envelope = MessageContentFuzzer.envelope(originalTimestamp),
|
envelope = MessageContentFuzzer.envelope(originalTimestamp),
|
||||||
content = syncContent,
|
content = syncContent,
|
||||||
|
@ -112,7 +112,7 @@ class EditMessageSyncProcessorTest {
|
||||||
|
|
||||||
testResult.runSync(listOf(syncTextMessage, syncEditMessage))
|
testResult.runSync(listOf(syncTextMessage, syncEditMessage))
|
||||||
|
|
||||||
SignalDatabase.recipients.setExpireMessages(toRecipient.id, (content.dataMessage?.expireTimer ?: 0) / 1000)
|
SignalDatabase.recipients.setExpireMessages(toRecipient.id, (content.dataMessage?.expireTimer ?: 0) / 1000, content.dataMessage?.expireTimerVersion ?: 1)
|
||||||
val originalTextMessage = OutgoingMessage(
|
val originalTextMessage = OutgoingMessage(
|
||||||
threadRecipient = toRecipient,
|
threadRecipient = toRecipient,
|
||||||
sentTimeMillis = originalTimestamp,
|
sentTimeMillis = originalTimestamp,
|
||||||
|
|
|
@ -141,7 +141,7 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
|
||||||
val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i)))
|
val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i)))
|
||||||
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i"))
|
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i"))
|
||||||
SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew())
|
SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew())
|
||||||
SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, false))
|
SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, false, true))
|
||||||
SignalDatabase.recipients.setProfileSharing(recipientId, true)
|
SignalDatabase.recipients.setProfileSharing(recipientId, true)
|
||||||
SignalDatabase.recipients.markRegistered(recipientId, aci)
|
SignalDatabase.recipients.markRegistered(recipientId, aci)
|
||||||
val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair()
|
val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair()
|
||||||
|
|
|
@ -96,6 +96,7 @@ class ConversationElementGenerator {
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
0,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
null,
|
null,
|
||||||
|
|
|
@ -11,7 +11,8 @@ object AppCapabilities {
|
||||||
fun getCapabilities(storageCapable: Boolean): AccountAttributes.Capabilities {
|
fun getCapabilities(storageCapable: Boolean): AccountAttributes.Capabilities {
|
||||||
return AccountAttributes.Capabilities(
|
return AccountAttributes.Capabilities(
|
||||||
storage = storageCapable,
|
storage = storageCapable,
|
||||||
deleteSync = true
|
deleteSync = true,
|
||||||
|
expireTimerVersion = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender
|
import org.thoughtcrime.securesms.sms.MessageSender
|
||||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||||
|
import org.thoughtcrime.securesms.util.ExpirationTimerUtil
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
private val TAG: String = Log.tag(ExpireTimerSettingsRepository::class.java)
|
private val TAG: String = Log.tag(ExpireTimerSettingsRepository::class.java)
|
||||||
|
@ -38,8 +39,8 @@ class ExpireTimerSettingsRepository(val context: Context) {
|
||||||
consumer.invoke(Result.failure(e))
|
consumer.invoke(Result.failure(e))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
SignalDatabase.recipients.setExpireMessages(recipientId, newExpirationTime)
|
val expireTimerVersion = ExpirationTimerUtil.setExpirationTimer(recipientId, newExpirationTime)
|
||||||
val outgoingMessage = OutgoingMessage.expirationUpdateMessage(Recipient.resolved(recipientId), System.currentTimeMillis(), newExpirationTime * 1000L)
|
val outgoingMessage = OutgoingMessage.expirationUpdateMessage(Recipient.resolved(recipientId), System.currentTimeMillis(), newExpirationTime * 1000L, expireTimerVersion)
|
||||||
MessageSender.send(context, outgoingMessage, getThreadId(recipientId), MessageSender.SendType.SIGNAL, null, null)
|
MessageSender.send(context, outgoingMessage, getThreadId(recipientId), MessageSender.SendType.SIGNAL, null, null)
|
||||||
consumer.invoke(Result.success(newExpirationTime))
|
consumer.invoke(Result.success(newExpirationTime))
|
||||||
}
|
}
|
||||||
|
|
|
@ -341,8 +341,9 @@ class InternalConversationSettingsFragment : DSLSettingsFragment(
|
||||||
|
|
||||||
return if (capabilities != null) {
|
return if (capabilities != null) {
|
||||||
TextUtils.concat(
|
TextUtils.concat(
|
||||||
|
colorize("DeleteSync", capabilities.deleteSync),
|
||||||
", ",
|
", ",
|
||||||
colorize("DeleteSync", capabilities.deleteSync)
|
colorize("Expire Timer Version", capabilities.versionedExpirationTimer)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
"Recipient not found!"
|
"Recipient not found!"
|
||||||
|
|
|
@ -716,7 +716,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupState?.disappearingMessagesTimer != null) {
|
if (groupState?.disappearingMessagesTimer != null) {
|
||||||
recipients.setExpireMessages(groupRecipientId, groupState.disappearingMessagesTimer!!.duration)
|
recipients.setExpireMessagesForGroup(groupRecipientId, groupState.disappearingMessagesTimer!!.duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupId.isMms || Recipient.resolved(groupRecipientId).isProfileSharing) {
|
if (groupId.isMms || Recipient.resolved(groupRecipientId).isProfileSharing) {
|
||||||
|
@ -843,7 +843,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decryptedGroup.disappearingMessagesTimer != null) {
|
if (decryptedGroup.disappearingMessagesTimer != null) {
|
||||||
recipients.setExpireMessages(groupRecipientId, decryptedGroup.disappearingMessagesTimer!!.duration)
|
recipients.setExpireMessagesForGroup(groupRecipientId, decryptedGroup.disappearingMessagesTimer!!.duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupId.isMms || Recipient.resolved(groupRecipientId).isProfileSharing) {
|
if (groupId.isMms || Recipient.resolved(groupRecipientId).isProfileSharing) {
|
||||||
|
|
|
@ -169,6 +169,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||||
const val SMS_SUBSCRIPTION_ID = "subscription_id"
|
const val SMS_SUBSCRIPTION_ID = "subscription_id"
|
||||||
const val EXPIRES_IN = "expires_in"
|
const val EXPIRES_IN = "expires_in"
|
||||||
const val EXPIRE_STARTED = "expire_started"
|
const val EXPIRE_STARTED = "expire_started"
|
||||||
|
const val EXPIRE_TIMER_VERSION = "expire_timer_version"
|
||||||
const val NOTIFIED = "notified"
|
const val NOTIFIED = "notified"
|
||||||
const val NOTIFIED_TIMESTAMP = "notified_timestamp"
|
const val NOTIFIED_TIMESTAMP = "notified_timestamp"
|
||||||
const val UNIDENTIFIED = "unidentified"
|
const val UNIDENTIFIED = "unidentified"
|
||||||
|
@ -264,7 +265,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||||
$LATEST_REVISION_ID INTEGER DEFAULT NULL REFERENCES $TABLE_NAME ($ID) ON DELETE CASCADE,
|
$LATEST_REVISION_ID INTEGER DEFAULT NULL REFERENCES $TABLE_NAME ($ID) ON DELETE CASCADE,
|
||||||
$ORIGINAL_MESSAGE_ID INTEGER DEFAULT NULL REFERENCES $TABLE_NAME ($ID) ON DELETE CASCADE,
|
$ORIGINAL_MESSAGE_ID INTEGER DEFAULT NULL REFERENCES $TABLE_NAME ($ID) ON DELETE CASCADE,
|
||||||
$REVISION_NUMBER INTEGER DEFAULT 0,
|
$REVISION_NUMBER INTEGER DEFAULT 0,
|
||||||
$MESSAGE_EXTRAS BLOB DEFAULT NULL
|
$MESSAGE_EXTRAS BLOB DEFAULT NULL,
|
||||||
|
$EXPIRE_TIMER_VERSION INTEGER DEFAULT 1 NOT NULL
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -321,6 +323,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||||
SMS_SUBSCRIPTION_ID,
|
SMS_SUBSCRIPTION_ID,
|
||||||
EXPIRES_IN,
|
EXPIRES_IN,
|
||||||
EXPIRE_STARTED,
|
EXPIRE_STARTED,
|
||||||
|
EXPIRE_TIMER_VERSION,
|
||||||
NOTIFIED,
|
NOTIFIED,
|
||||||
QUOTE_ID,
|
QUOTE_ID,
|
||||||
QUOTE_AUTHOR,
|
QUOTE_AUTHOR,
|
||||||
|
@ -2404,6 +2407,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||||
val timestamp = cursor.requireLong(DATE_SENT)
|
val timestamp = cursor.requireLong(DATE_SENT)
|
||||||
val subscriptionId = cursor.requireInt(SMS_SUBSCRIPTION_ID)
|
val subscriptionId = cursor.requireInt(SMS_SUBSCRIPTION_ID)
|
||||||
val expiresIn = cursor.requireLong(EXPIRES_IN)
|
val expiresIn = cursor.requireLong(EXPIRES_IN)
|
||||||
|
val expireTimerVersion = cursor.requireInt(EXPIRE_TIMER_VERSION)
|
||||||
val viewOnce = cursor.requireLong(VIEW_ONCE) == 1L
|
val viewOnce = cursor.requireLong(VIEW_ONCE) == 1L
|
||||||
val threadId = cursor.requireLong(THREAD_ID)
|
val threadId = cursor.requireLong(THREAD_ID)
|
||||||
val threadRecipient = Recipient.resolved(threads.getRecipientIdForThreadId(threadId)!!)
|
val threadRecipient = Recipient.resolved(threads.getRecipientIdForThreadId(threadId)!!)
|
||||||
|
@ -2480,7 +2484,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||||
OutgoingMessage.expirationUpdateMessage(
|
OutgoingMessage.expirationUpdateMessage(
|
||||||
threadRecipient = threadRecipient,
|
threadRecipient = threadRecipient,
|
||||||
sentTimeMillis = timestamp,
|
sentTimeMillis = timestamp,
|
||||||
expiresIn = expiresIn
|
expiresIn = expiresIn,
|
||||||
|
expireTimerVersion = expireTimerVersion
|
||||||
)
|
)
|
||||||
} else if (MessageTypes.isPaymentsNotification(outboxType)) {
|
} else if (MessageTypes.isPaymentsNotification(outboxType)) {
|
||||||
OutgoingMessage.paymentNotificationMessage(
|
OutgoingMessage.paymentNotificationMessage(
|
||||||
|
@ -2539,6 +2544,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||||
attachments = attachments,
|
attachments = attachments,
|
||||||
timestamp = timestamp,
|
timestamp = timestamp,
|
||||||
expiresIn = expiresIn,
|
expiresIn = expiresIn,
|
||||||
|
expireTimerVersion = expireTimerVersion,
|
||||||
viewOnce = viewOnce,
|
viewOnce = viewOnce,
|
||||||
distributionType = distributionType,
|
distributionType = distributionType,
|
||||||
storyType = storyType,
|
storyType = storyType,
|
||||||
|
@ -2971,6 +2977,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||||
contentValues.put(DATE_RECEIVED, editedMessage?.dateReceived ?: System.currentTimeMillis())
|
contentValues.put(DATE_RECEIVED, editedMessage?.dateReceived ?: System.currentTimeMillis())
|
||||||
contentValues.put(SMS_SUBSCRIPTION_ID, message.subscriptionId)
|
contentValues.put(SMS_SUBSCRIPTION_ID, message.subscriptionId)
|
||||||
contentValues.put(EXPIRES_IN, editedMessage?.expiresIn ?: message.expiresIn)
|
contentValues.put(EXPIRES_IN, editedMessage?.expiresIn ?: message.expiresIn)
|
||||||
|
contentValues.put(EXPIRE_TIMER_VERSION, editedMessage?.expireTimerVersion ?: message.expireTimerVersion)
|
||||||
contentValues.put(VIEW_ONCE, message.isViewOnce)
|
contentValues.put(VIEW_ONCE, message.isViewOnce)
|
||||||
contentValues.put(FROM_RECIPIENT_ID, Recipient.self().id.serialize())
|
contentValues.put(FROM_RECIPIENT_ID, Recipient.self().id.serialize())
|
||||||
contentValues.put(FROM_DEVICE_ID, SignalStore.account.deviceId)
|
contentValues.put(FROM_DEVICE_ID, SignalStore.account.deviceId)
|
||||||
|
@ -5214,6 +5221,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||||
val subscriptionId = cursor.requireInt(SMS_SUBSCRIPTION_ID)
|
val subscriptionId = cursor.requireInt(SMS_SUBSCRIPTION_ID)
|
||||||
val expiresIn = cursor.requireLong(EXPIRES_IN)
|
val expiresIn = cursor.requireLong(EXPIRES_IN)
|
||||||
val expireStarted = cursor.requireLong(EXPIRE_STARTED)
|
val expireStarted = cursor.requireLong(EXPIRE_STARTED)
|
||||||
|
val expireTimerVersion = cursor.requireInt(EXPIRE_TIMER_VERSION)
|
||||||
val unidentified = cursor.requireBoolean(UNIDENTIFIED)
|
val unidentified = cursor.requireBoolean(UNIDENTIFIED)
|
||||||
val isViewOnce = cursor.requireBoolean(VIEW_ONCE)
|
val isViewOnce = cursor.requireBoolean(VIEW_ONCE)
|
||||||
val remoteDelete = cursor.requireBoolean(REMOTE_DELETED)
|
val remoteDelete = cursor.requireBoolean(REMOTE_DELETED)
|
||||||
|
@ -5296,6 +5304,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
expireStarted,
|
expireStarted,
|
||||||
|
expireTimerVersion,
|
||||||
isViewOnce,
|
isViewOnce,
|
||||||
hasReadReceipt,
|
hasReadReceipt,
|
||||||
quote,
|
quote,
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.signal.core.util.orNull
|
||||||
import org.signal.core.util.readToList
|
import org.signal.core.util.readToList
|
||||||
import org.signal.core.util.readToSet
|
import org.signal.core.util.readToSet
|
||||||
import org.signal.core.util.readToSingleBoolean
|
import org.signal.core.util.readToSingleBoolean
|
||||||
|
import org.signal.core.util.readToSingleInt
|
||||||
import org.signal.core.util.readToSingleLong
|
import org.signal.core.util.readToSingleLong
|
||||||
import org.signal.core.util.readToSingleObject
|
import org.signal.core.util.readToSingleObject
|
||||||
import org.signal.core.util.requireBlob
|
import org.signal.core.util.requireBlob
|
||||||
|
@ -166,6 +167,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||||
const val CALL_VIBRATE = "call_vibrate"
|
const val CALL_VIBRATE = "call_vibrate"
|
||||||
const val MUTE_UNTIL = "mute_until"
|
const val MUTE_UNTIL = "mute_until"
|
||||||
const val MESSAGE_EXPIRATION_TIME = "message_expiration_time"
|
const val MESSAGE_EXPIRATION_TIME = "message_expiration_time"
|
||||||
|
const val MESSAGE_EXPIRATION_TIME_VERSION = "message_expiration_time_version"
|
||||||
const val SEALED_SENDER_MODE = "sealed_sender_mode"
|
const val SEALED_SENDER_MODE = "sealed_sender_mode"
|
||||||
const val STORAGE_SERVICE_ID = "storage_service_id"
|
const val STORAGE_SERVICE_ID = "storage_service_id"
|
||||||
const val STORAGE_SERVICE_PROTO = "storage_service_proto"
|
const val STORAGE_SERVICE_PROTO = "storage_service_proto"
|
||||||
|
@ -263,7 +265,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||||
$NICKNAME_GIVEN_NAME TEXT DEFAULT NULL,
|
$NICKNAME_GIVEN_NAME TEXT DEFAULT NULL,
|
||||||
$NICKNAME_FAMILY_NAME TEXT DEFAULT NULL,
|
$NICKNAME_FAMILY_NAME TEXT DEFAULT NULL,
|
||||||
$NICKNAME_JOINED_NAME TEXT DEFAULT NULL,
|
$NICKNAME_JOINED_NAME TEXT DEFAULT NULL,
|
||||||
$NOTE TEXT DEFAULT NULL
|
$NOTE TEXT DEFAULT NULL,
|
||||||
|
$MESSAGE_EXPIRATION_TIME_VERSION INTEGER DEFAULT 1 NOT NULL
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -307,6 +310,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||||
CALL_VIBRATE,
|
CALL_VIBRATE,
|
||||||
MUTE_UNTIL,
|
MUTE_UNTIL,
|
||||||
MESSAGE_EXPIRATION_TIME,
|
MESSAGE_EXPIRATION_TIME,
|
||||||
|
MESSAGE_EXPIRATION_TIME_VERSION,
|
||||||
SEALED_SENDER_MODE,
|
SEALED_SENDER_MODE,
|
||||||
STORAGE_SERVICE_ID,
|
STORAGE_SERVICE_ID,
|
||||||
MENTION_SETTING,
|
MENTION_SETTING,
|
||||||
|
@ -410,6 +414,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||||
fun maskCapabilitiesToLong(capabilities: SignalServiceProfile.Capabilities): Long {
|
fun maskCapabilitiesToLong(capabilities: SignalServiceProfile.Capabilities): Long {
|
||||||
var value: Long = 0
|
var value: Long = 0
|
||||||
value = Bitmask.update(value, Capabilities.DELETE_SYNC, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isDeleteSync).serialize().toLong())
|
value = Bitmask.update(value, Capabilities.DELETE_SYNC, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isDeleteSync).serialize().toLong())
|
||||||
|
value = Bitmask.update(value, Capabilities.VERSIONED_EXPIRATION_TIMER, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isVersionedExpirationTimer).serialize().toLong())
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1473,7 +1478,27 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||||
StorageSyncHelper.scheduleSyncForDataChange()
|
StorageSyncHelper.scheduleSyncForDataChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setExpireMessages(id: RecipientId, expiration: Int) {
|
fun setExpireMessagesAndIncrementVersion(id: RecipientId, expiration: Int): Int {
|
||||||
|
val version = writableDatabase.rawQuery(
|
||||||
|
"""
|
||||||
|
UPDATE $TABLE_NAME
|
||||||
|
SET $MESSAGE_EXPIRATION_TIME = $expiration,
|
||||||
|
$MESSAGE_EXPIRATION_TIME_VERSION = MIN($MESSAGE_EXPIRATION_TIME_VERSION + 1, ${Int.MAX_VALUE})
|
||||||
|
WHERE $ID = ${id.serialize()}
|
||||||
|
RETURNING $MESSAGE_EXPIRATION_TIME_VERSION
|
||||||
|
""",
|
||||||
|
null
|
||||||
|
).readToSingleInt(defaultValue = 1)
|
||||||
|
|
||||||
|
AppDependencies.databaseObserver.notifyRecipientChanged(id)
|
||||||
|
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the expiration timer without incrementing the version. Will eventually be removed once everyone has the ability to understand expireTimerVersions.
|
||||||
|
*/
|
||||||
|
fun setExpireMessagesWithoutIncrementingVersion(id: RecipientId, expiration: Int) {
|
||||||
val values = ContentValues(1).apply {
|
val values = ContentValues(1).apply {
|
||||||
put(MESSAGE_EXPIRATION_TIME, expiration)
|
put(MESSAGE_EXPIRATION_TIME, expiration)
|
||||||
}
|
}
|
||||||
|
@ -1482,6 +1507,23 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Groups do not have an expireTimerVersion, and therefore we do not need to provide them.
|
||||||
|
*/
|
||||||
|
fun setExpireMessagesForGroup(id: RecipientId, expiration: Int) {
|
||||||
|
setExpireMessages(id, expiration, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setExpireMessages(id: RecipientId, expiration: Int, expirationVersion: Int) {
|
||||||
|
val values = contentValuesOf(
|
||||||
|
MESSAGE_EXPIRATION_TIME to expiration,
|
||||||
|
MESSAGE_EXPIRATION_TIME_VERSION to expirationVersion
|
||||||
|
)
|
||||||
|
if (update(id, values)) {
|
||||||
|
AppDependencies.databaseObserver.notifyRecipientChanged(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun setSealedSenderAccessMode(id: RecipientId, sealedSenderAccessMode: SealedSenderAccessMode) {
|
fun setSealedSenderAccessMode(id: RecipientId, sealedSenderAccessMode: SealedSenderAccessMode) {
|
||||||
val values = ContentValues(1).apply {
|
val values = ContentValues(1).apply {
|
||||||
put(SEALED_SENDER_MODE, sealedSenderAccessMode.mode)
|
put(SEALED_SENDER_MODE, sealedSenderAccessMode.mode)
|
||||||
|
@ -3988,6 +4030,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||||
AVATAR_COLOR to primaryRecord.avatarColor.serialize(),
|
AVATAR_COLOR to primaryRecord.avatarColor.serialize(),
|
||||||
CUSTOM_CHAT_COLORS_ID to Optional.ofNullable(primaryRecord.chatColors).or(Optional.ofNullable(secondaryRecord.chatColors)).map { colors: ChatColors? -> colors!!.id.longValue }.orElse(null),
|
CUSTOM_CHAT_COLORS_ID to Optional.ofNullable(primaryRecord.chatColors).or(Optional.ofNullable(secondaryRecord.chatColors)).map { colors: ChatColors? -> colors!!.id.longValue }.orElse(null),
|
||||||
MESSAGE_EXPIRATION_TIME to if (primaryRecord.expireMessages > 0) primaryRecord.expireMessages else secondaryRecord.expireMessages,
|
MESSAGE_EXPIRATION_TIME to if (primaryRecord.expireMessages > 0) primaryRecord.expireMessages else secondaryRecord.expireMessages,
|
||||||
|
MESSAGE_EXPIRATION_TIME_VERSION to max(primaryRecord.expireTimerVersion, secondaryRecord.expireTimerVersion),
|
||||||
REGISTERED to RegisteredState.REGISTERED.id,
|
REGISTERED to RegisteredState.REGISTERED.id,
|
||||||
SYSTEM_GIVEN_NAME to secondaryRecord.systemProfileName.givenName,
|
SYSTEM_GIVEN_NAME to secondaryRecord.systemProfileName.givenName,
|
||||||
SYSTEM_FAMILY_NAME to secondaryRecord.systemProfileName.familyName,
|
SYSTEM_FAMILY_NAME to secondaryRecord.systemProfileName.familyName,
|
||||||
|
@ -4591,6 +4634,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||||
// const val PNP = 7
|
// const val PNP = 7
|
||||||
// const val PAYMENT_ACTIVATION = 8
|
// const val PAYMENT_ACTIVATION = 8
|
||||||
const val DELETE_SYNC = 9
|
const val DELETE_SYNC = 9
|
||||||
|
const val VERSIONED_EXPIRATION_TIMER = 10
|
||||||
|
|
||||||
// IMPORTANT: We cannot sore more than 32 capabilities in the bitmask.
|
// IMPORTANT: We cannot sore more than 32 capabilities in the bitmask.
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,7 @@ object RecipientTableCursorUtil {
|
||||||
messageRingtone = Util.uri(cursor.requireString(RecipientTable.MESSAGE_RINGTONE)),
|
messageRingtone = Util.uri(cursor.requireString(RecipientTable.MESSAGE_RINGTONE)),
|
||||||
callRingtone = Util.uri(cursor.requireString(RecipientTable.CALL_RINGTONE)),
|
callRingtone = Util.uri(cursor.requireString(RecipientTable.CALL_RINGTONE)),
|
||||||
expireMessages = cursor.requireInt(RecipientTable.MESSAGE_EXPIRATION_TIME),
|
expireMessages = cursor.requireInt(RecipientTable.MESSAGE_EXPIRATION_TIME),
|
||||||
|
expireTimerVersion = cursor.requireInt(RecipientTable.MESSAGE_EXPIRATION_TIME_VERSION),
|
||||||
registered = RegisteredState.fromId(cursor.requireInt(RecipientTable.REGISTERED)),
|
registered = RegisteredState.fromId(cursor.requireInt(RecipientTable.REGISTERED)),
|
||||||
profileKey = profileKey,
|
profileKey = profileKey,
|
||||||
expiringProfileKeyCredential = expiringProfileKeyCredential,
|
expiringProfileKeyCredential = expiringProfileKeyCredential,
|
||||||
|
@ -175,7 +176,8 @@ object RecipientTableCursorUtil {
|
||||||
val capabilities = cursor.requireLong(RecipientTable.CAPABILITIES)
|
val capabilities = cursor.requireLong(RecipientTable.CAPABILITIES)
|
||||||
return RecipientRecord.Capabilities(
|
return RecipientRecord.Capabilities(
|
||||||
rawBits = capabilities,
|
rawBits = capabilities,
|
||||||
deleteSync = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.DELETE_SYNC, Capabilities.BIT_LENGTH).toInt())
|
deleteSync = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.DELETE_SYNC, Capabilities.BIT_LENGTH).toInt()),
|
||||||
|
versionedExpirationTimer = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.VERSIONED_EXPIRATION_TIMER, Capabilities.BIT_LENGTH).toInt())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V237_ResetGroupForc
|
||||||
import org.thoughtcrime.securesms.database.helpers.migration.V238_AddGroupSendEndorsementsColumns
|
import org.thoughtcrime.securesms.database.helpers.migration.V238_AddGroupSendEndorsementsColumns
|
||||||
import org.thoughtcrime.securesms.database.helpers.migration.V239_MessageFullTextSearchEmojiSupport
|
import org.thoughtcrime.securesms.database.helpers.migration.V239_MessageFullTextSearchEmojiSupport
|
||||||
import org.thoughtcrime.securesms.database.helpers.migration.V240_MessageFullTextSearchSecureDelete
|
import org.thoughtcrime.securesms.database.helpers.migration.V240_MessageFullTextSearchSecureDelete
|
||||||
|
import org.thoughtcrime.securesms.database.helpers.migration.V241_ExpireTimerVersion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
|
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
|
||||||
|
@ -198,10 +199,11 @@ object SignalDatabaseMigrations {
|
||||||
237 to V237_ResetGroupForceUpdateTimestamps,
|
237 to V237_ResetGroupForceUpdateTimestamps,
|
||||||
238 to V238_AddGroupSendEndorsementsColumns,
|
238 to V238_AddGroupSendEndorsementsColumns,
|
||||||
239 to V239_MessageFullTextSearchEmojiSupport,
|
239 to V239_MessageFullTextSearchEmojiSupport,
|
||||||
240 to V240_MessageFullTextSearchSecureDelete
|
240 to V240_MessageFullTextSearchSecureDelete,
|
||||||
|
241 to V241_ExpireTimerVersion
|
||||||
)
|
)
|
||||||
|
|
||||||
const val DATABASE_VERSION = 240
|
const val DATABASE_VERSION = 241
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.database.helpers.migration
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import net.zetetic.database.sqlcipher.SQLiteDatabase
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration for new field tracking expiration timer version.
|
||||||
|
*/
|
||||||
|
object V241_ExpireTimerVersion : SignalDatabaseMigration {
|
||||||
|
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
|
db.execSQL("ALTER TABLE message ADD COLUMN expire_timer_version INTEGER DEFAULT 1 NOT NULL;")
|
||||||
|
db.execSQL("ALTER TABLE recipient ADD COLUMN message_expiration_time_version INTEGER DEFAULT 1 NOT NULL;")
|
||||||
|
db.execSQL(
|
||||||
|
"""
|
||||||
|
UPDATE recipient
|
||||||
|
SET message_expiration_time_version = 2
|
||||||
|
WHERE message_expiration_time > 0
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,6 +48,7 @@ public class InMemoryMessageRecord extends MessageRecord {
|
||||||
-1,
|
-1,
|
||||||
0,
|
0,
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
|
1,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
Collections.emptyList(),
|
Collections.emptyList(),
|
||||||
|
|
|
@ -99,6 +99,7 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||||
private final int subscriptionId;
|
private final int subscriptionId;
|
||||||
private final long expiresIn;
|
private final long expiresIn;
|
||||||
private final long expireStarted;
|
private final long expireStarted;
|
||||||
|
private final int expireTimerVersion;
|
||||||
private final boolean unidentified;
|
private final boolean unidentified;
|
||||||
private final List<ReactionRecord> reactions;
|
private final List<ReactionRecord> reactions;
|
||||||
private final long serverTimestamp;
|
private final long serverTimestamp;
|
||||||
|
@ -119,6 +120,7 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||||
int subscriptionId,
|
int subscriptionId,
|
||||||
long expiresIn,
|
long expiresIn,
|
||||||
long expireStarted,
|
long expireStarted,
|
||||||
|
int expireTimerVersion,
|
||||||
boolean hasReadReceipt,
|
boolean hasReadReceipt,
|
||||||
boolean unidentified,
|
boolean unidentified,
|
||||||
@NonNull List<ReactionRecord> reactions,
|
@NonNull List<ReactionRecord> reactions,
|
||||||
|
@ -140,6 +142,7 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||||
this.subscriptionId = subscriptionId;
|
this.subscriptionId = subscriptionId;
|
||||||
this.expiresIn = expiresIn;
|
this.expiresIn = expiresIn;
|
||||||
this.expireStarted = expireStarted;
|
this.expireStarted = expireStarted;
|
||||||
|
this.expireTimerVersion = expireTimerVersion;
|
||||||
this.unidentified = unidentified;
|
this.unidentified = unidentified;
|
||||||
this.reactions = reactions;
|
this.reactions = reactions;
|
||||||
this.serverTimestamp = dateServer;
|
this.serverTimestamp = dateServer;
|
||||||
|
@ -754,6 +757,10 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||||
return expireStarted;
|
return expireStarted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getExpireTimerVersion() {
|
||||||
|
return expireTimerVersion;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isUnidentified() {
|
public boolean isUnidentified() {
|
||||||
return unidentified;
|
return unidentified;
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||||
int subscriptionId,
|
int subscriptionId,
|
||||||
long expiresIn,
|
long expiresIn,
|
||||||
long expireStarted,
|
long expireStarted,
|
||||||
|
int expireTimerVersion,
|
||||||
boolean viewOnce,
|
boolean viewOnce,
|
||||||
boolean hasReadReceipt,
|
boolean hasReadReceipt,
|
||||||
@Nullable Quote quote,
|
@Nullable Quote quote,
|
||||||
|
@ -121,7 +122,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||||
{
|
{
|
||||||
super(id, body, fromRecipient, fromDeviceId, toRecipient,
|
super(id, body, fromRecipient, fromDeviceId, toRecipient,
|
||||||
dateSent, dateReceived, dateServer, threadId, Status.STATUS_NONE, hasDeliveryReceipt,
|
dateSent, dateReceived, dateServer, threadId, Status.STATUS_NONE, hasDeliveryReceipt,
|
||||||
mailbox, mismatches, failures, subscriptionId, expiresIn, expireStarted, hasReadReceipt,
|
mailbox, mismatches, failures, subscriptionId, expiresIn, expireStarted, expireTimerVersion, hasReadReceipt,
|
||||||
unidentified, reactions, remoteDelete, notifiedTimestamp, viewed, receiptTimestamp, originalMessageId, revisionNumber, messageExtras);
|
unidentified, reactions, remoteDelete, notifiedTimestamp, viewed, receiptTimestamp, originalMessageId, revisionNumber, messageExtras);
|
||||||
|
|
||||||
this.slideDeck = slideDeck;
|
this.slideDeck = slideDeck;
|
||||||
|
@ -323,7 +324,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||||
|
|
||||||
public @NonNull MmsMessageRecord withReactions(@NonNull List<ReactionRecord> reactions) {
|
public @NonNull MmsMessageRecord withReactions(@NonNull List<ReactionRecord> reactions) {
|
||||||
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
|
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
|
||||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
|
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||||
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), reactions, isRemoteDelete(), mentionsSelf,
|
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), reactions, isRemoteDelete(), mentionsSelf,
|
||||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
|
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
|
||||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
|
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
|
||||||
|
@ -331,7 +332,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||||
|
|
||||||
public @NonNull MmsMessageRecord withoutQuote() {
|
public @NonNull MmsMessageRecord withoutQuote() {
|
||||||
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
|
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
|
||||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
|
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||||
hasReadReceipt(), null, getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
hasReadReceipt(), null, getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
||||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
|
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
|
||||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
|
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
|
||||||
|
@ -353,7 +354,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||||
SlideDeck slideDeck = MessageTable.MmsReader.buildSlideDeck(slideAttachments);
|
SlideDeck slideDeck = MessageTable.MmsReader.buildSlideDeck(slideAttachments);
|
||||||
|
|
||||||
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), slideDeck,
|
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), slideDeck,
|
||||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
|
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||||
hasReadReceipt(), quote, contacts, linkPreviews, isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
hasReadReceipt(), quote, contacts, linkPreviews, isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
||||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
|
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
|
||||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
|
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
|
||||||
|
@ -361,7 +362,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||||
|
|
||||||
public @NonNull MmsMessageRecord withPayment(@NonNull Payment payment) {
|
public @NonNull MmsMessageRecord withPayment(@NonNull Payment payment) {
|
||||||
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
|
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
|
||||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
|
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||||
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
||||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), payment, getCall(), getScheduledDate(), getLatestRevisionId(),
|
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), payment, getCall(), getScheduledDate(), getLatestRevisionId(),
|
||||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
|
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
|
||||||
|
@ -370,7 +371,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||||
|
|
||||||
public @NonNull MmsMessageRecord withCall(@Nullable CallTable.Call call) {
|
public @NonNull MmsMessageRecord withCall(@Nullable CallTable.Call call) {
|
||||||
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
|
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
|
||||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
|
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||||
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
||||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), call, getScheduledDate(), getLatestRevisionId(),
|
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), call, getScheduledDate(), getLatestRevisionId(),
|
||||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
|
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
|
||||||
|
|
|
@ -43,6 +43,7 @@ data class RecipientRecord(
|
||||||
val messageRingtone: Uri?,
|
val messageRingtone: Uri?,
|
||||||
val callRingtone: Uri?,
|
val callRingtone: Uri?,
|
||||||
val expireMessages: Int,
|
val expireMessages: Int,
|
||||||
|
val expireTimerVersion: Int,
|
||||||
val registered: RegisteredState,
|
val registered: RegisteredState,
|
||||||
val profileKey: ByteArray?,
|
val profileKey: ByteArray?,
|
||||||
val expiringProfileKeyCredential: ExpiringProfileKeyCredential?,
|
val expiringProfileKeyCredential: ExpiringProfileKeyCredential?,
|
||||||
|
@ -119,13 +120,15 @@ data class RecipientRecord(
|
||||||
|
|
||||||
data class Capabilities(
|
data class Capabilities(
|
||||||
val rawBits: Long,
|
val rawBits: Long,
|
||||||
val deleteSync: Recipient.Capability
|
val deleteSync: Recipient.Capability,
|
||||||
|
val versionedExpirationTimer: Recipient.Capability
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmField
|
@JvmField
|
||||||
val UNKNOWN = Capabilities(
|
val UNKNOWN = Capabilities(
|
||||||
0,
|
rawBits = 0,
|
||||||
Recipient.Capability.UNKNOWN
|
deleteSync = Recipient.Capability.UNKNOWN,
|
||||||
|
versionedExpirationTimer = Recipient.Capability.UNKNOWN
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -266,6 +266,7 @@ public class IndividualSendJob extends PushSendJob {
|
||||||
.withAttachments(serviceAttachments)
|
.withAttachments(serviceAttachments)
|
||||||
.withTimestamp(message.getSentTimeMillis())
|
.withTimestamp(message.getSentTimeMillis())
|
||||||
.withExpiration((int)(message.getExpiresIn() / 1000))
|
.withExpiration((int)(message.getExpiresIn() / 1000))
|
||||||
|
.withExpireTimerVersion(message.getExpireTimerVersion())
|
||||||
.withViewOnce(message.isViewOnce())
|
.withViewOnce(message.isViewOnce())
|
||||||
.withProfileKey(profileKey.orElse(null))
|
.withProfileKey(profileKey.orElse(null))
|
||||||
.withSticker(sticker.orElse(null))
|
.withSticker(sticker.orElse(null))
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.thoughtcrime.securesms.jobs
|
package org.thoughtcrime.securesms.jobs
|
||||||
|
|
||||||
|
import org.signal.core.util.isAbsent
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.signal.libsignal.protocol.InvalidMessageException
|
import org.signal.libsignal.protocol.InvalidMessageException
|
||||||
import org.thoughtcrime.securesms.database.IdentityTable.VerifiedStatus
|
import org.thoughtcrime.securesms.database.IdentityTable.VerifiedStatus
|
||||||
|
@ -92,7 +93,14 @@ class MultiDeviceContactSyncJob(parameters: Parameters, private val attachmentPo
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contact.expirationTimer.isPresent) {
|
if (contact.expirationTimer.isPresent) {
|
||||||
recipients.setExpireMessages(recipient.id, contact.expirationTimer.get())
|
if (contact.expirationTimerVersion.isPresent && contact.expirationTimerVersion.get() > recipient.expireTimerVersion) {
|
||||||
|
recipients.setExpireMessages(recipient.id, contact.expirationTimer.get(), contact.expirationTimerVersion.orElse(1))
|
||||||
|
} else if (contact.expirationTimerVersion.isAbsent()) {
|
||||||
|
// TODO [expireVersion] After unsupported builds expire, we can remove this branch
|
||||||
|
recipients.setExpireMessagesWithoutIncrementingVersion(recipient.id, contact.expirationTimer.get())
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "[ContactSync] ${recipient.id} was synced with an old expiration timer. Ignoring. Recieved: ${contact.expirationTimerVersion.get()} Current: ${recipient.expireTimerVersion}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contact.profileKey.isPresent) {
|
if (contact.profileKey.isPresent) {
|
||||||
|
|
|
@ -170,6 +170,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
|
||||||
ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey()),
|
ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey()),
|
||||||
recipient.getExpiresInSeconds() > 0 ? Optional.of(recipient.getExpiresInSeconds())
|
recipient.getExpiresInSeconds() > 0 ? Optional.of(recipient.getExpiresInSeconds())
|
||||||
: Optional.empty(),
|
: Optional.empty(),
|
||||||
|
Optional.of(recipient.getExpireTimerVersion()),
|
||||||
Optional.ofNullable(inboxPositions.get(recipientId)),
|
Optional.ofNullable(inboxPositions.get(recipientId)),
|
||||||
archived.contains(recipientId)));
|
archived.contains(recipientId)));
|
||||||
|
|
||||||
|
@ -219,13 +220,14 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
|
||||||
Set<RecipientId> archived = SignalDatabase.threads().getArchivedRecipients();
|
Set<RecipientId> archived = SignalDatabase.threads().getArchivedRecipients();
|
||||||
|
|
||||||
for (Recipient recipient : recipients) {
|
for (Recipient recipient : recipients) {
|
||||||
Optional<IdentityRecord> identity = AppDependencies.getProtocolStore().aci().identities().getIdentityRecord(recipient.getId());
|
Optional<IdentityRecord> identity = AppDependencies.getProtocolStore().aci().identities().getIdentityRecord(recipient.getId());
|
||||||
Optional<VerifiedMessage> verified = getVerifiedMessage(recipient, identity);
|
Optional<VerifiedMessage> verified = getVerifiedMessage(recipient, identity);
|
||||||
Optional<String> name = Optional.ofNullable(recipient.isSystemContact() ? recipient.getDisplayName(context) : recipient.getGroupName(context));
|
Optional<String> name = Optional.ofNullable(recipient.isSystemContact() ? recipient.getDisplayName(context) : recipient.getGroupName(context));
|
||||||
Optional<ProfileKey> profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey());
|
Optional<ProfileKey> profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey());
|
||||||
boolean blocked = recipient.isBlocked();
|
boolean blocked = recipient.isBlocked();
|
||||||
Optional<Integer> expireTimer = recipient.getExpiresInSeconds() > 0 ? Optional.of(recipient.getExpiresInSeconds()) : Optional.empty();
|
Optional<Integer> expireTimer = recipient.getExpiresInSeconds() > 0 ? Optional.of(recipient.getExpiresInSeconds()) : Optional.empty();
|
||||||
Optional<Integer> inboxPosition = Optional.ofNullable(inboxPositions.get(recipient.getId()));
|
Optional<Integer> expireTimerVersion = Optional.of(recipient.getExpireTimerVersion());
|
||||||
|
Optional<Integer> inboxPosition = Optional.ofNullable(inboxPositions.get(recipient.getId()));
|
||||||
|
|
||||||
out.write(new DeviceContact(recipient.getAci(),
|
out.write(new DeviceContact(recipient.getAci(),
|
||||||
recipient.getE164(),
|
recipient.getE164(),
|
||||||
|
@ -235,6 +237,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
|
||||||
verified,
|
verified,
|
||||||
profileKey,
|
profileKey,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
|
expireTimerVersion,
|
||||||
inboxPosition,
|
inboxPosition,
|
||||||
archived.contains(recipient.getId())));
|
archived.contains(recipient.getId())));
|
||||||
}
|
}
|
||||||
|
@ -252,6 +255,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
ProfileKeyUtil.profileKeyOptionalOrThrow(self.getProfileKey()),
|
ProfileKeyUtil.profileKeyOptionalOrThrow(self.getProfileKey()),
|
||||||
self.getExpiresInSeconds() > 0 ? Optional.of(self.getExpiresInSeconds()) : Optional.empty(),
|
self.getExpiresInSeconds() > 0 ? Optional.of(self.getExpiresInSeconds()) : Optional.empty(),
|
||||||
|
Optional.of(self.getExpireTimerVersion()),
|
||||||
Optional.ofNullable(inboxPositions.get(self.getId())),
|
Optional.ofNullable(inboxPositions.get(self.getId())),
|
||||||
archived.contains(self.getId())));
|
archived.contains(self.getId())));
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,7 @@ public class MultiDeviceProfileKeyUpdateJob extends BaseJob {
|
||||||
profileKey,
|
profileKey,
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
false));
|
false));
|
||||||
|
|
||||||
out.close();
|
out.close();
|
||||||
|
|
|
@ -221,6 +221,11 @@ public class RefreshOwnProfileJob extends BaseJob {
|
||||||
AppDependencies.getJobManager().add(new MultiDeviceProfileContentUpdateJob());
|
AppDependencies.getJobManager().add(new MultiDeviceProfileContentUpdateJob());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Recipient.self().getVersionedExpirationTimerCapability().isSupported() && capabilities.isVersionedExpirationTimer()) {
|
||||||
|
Log.d(TAG, "Transitioned to versioned expiration timer capable, notify linked devices in case we were the last one");
|
||||||
|
AppDependencies.getJobManager().add(new MultiDeviceProfileContentUpdateJob());
|
||||||
|
}
|
||||||
|
|
||||||
SignalDatabase.recipients().setCapabilities(Recipient.self().getId(), capabilities);
|
SignalDatabase.recipients().setCapabilities(Recipient.self().getId(), capabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -158,7 +158,7 @@ object DataMessageProcessor {
|
||||||
when {
|
when {
|
||||||
message.isInvalid -> handleInvalidMessage(context, senderRecipient.id, groupId, envelope.timestamp!!)
|
message.isInvalid -> handleInvalidMessage(context, senderRecipient.id, groupId, envelope.timestamp!!)
|
||||||
message.isEndSession -> insertResult = handleEndSessionMessage(context, senderRecipient.id, envelope, metadata)
|
message.isEndSession -> insertResult = handleEndSessionMessage(context, senderRecipient.id, envelope, metadata)
|
||||||
message.isExpirationUpdate -> insertResult = handleExpirationUpdate(envelope, metadata, senderRecipient.id, threadRecipient.id, groupId, message.expireTimerDuration, receivedTime, false)
|
message.isExpirationUpdate -> insertResult = handleExpirationUpdate(envelope, metadata, senderRecipient, threadRecipient.id, groupId, message.expireTimerDuration, message.expireTimerVersion, receivedTime, false)
|
||||||
message.isStoryReaction -> insertResult = handleStoryReaction(context, envelope, metadata, message, senderRecipient.id, groupId)
|
message.isStoryReaction -> insertResult = handleStoryReaction(context, envelope, metadata, message, senderRecipient.id, groupId)
|
||||||
message.reaction != null -> messageId = handleReaction(context, envelope, message, senderRecipient.id, earlyMessageCacheEntry)
|
message.reaction != null -> messageId = handleReaction(context, envelope, message, senderRecipient.id, earlyMessageCacheEntry)
|
||||||
message.hasRemoteDelete -> messageId = handleRemoteDelete(context, envelope, message, senderRecipient.id, earlyMessageCacheEntry)
|
message.hasRemoteDelete -> messageId = handleRemoteDelete(context, envelope, message, senderRecipient.id, earlyMessageCacheEntry)
|
||||||
|
@ -321,10 +321,11 @@ object DataMessageProcessor {
|
||||||
private fun handleExpirationUpdate(
|
private fun handleExpirationUpdate(
|
||||||
envelope: Envelope,
|
envelope: Envelope,
|
||||||
metadata: EnvelopeMetadata,
|
metadata: EnvelopeMetadata,
|
||||||
senderRecipientId: RecipientId,
|
senderRecipient: Recipient,
|
||||||
threadRecipientId: RecipientId,
|
threadRecipientId: RecipientId,
|
||||||
groupId: GroupId.V2?,
|
groupId: GroupId.V2?,
|
||||||
expiresIn: Duration,
|
expiresIn: Duration,
|
||||||
|
expireTimerVersion: Int?,
|
||||||
receivedTime: Long,
|
receivedTime: Long,
|
||||||
sideEffect: Boolean
|
sideEffect: Boolean
|
||||||
): InsertResult? {
|
): InsertResult? {
|
||||||
|
@ -340,10 +341,15 @@ object DataMessageProcessor {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (expireTimerVersion != null && expireTimerVersion < senderRecipient.expireTimerVersion) {
|
||||||
|
log(envelope.timestamp!!, "Old expireTimerVersion. Received: $expireTimerVersion, Current: ${senderRecipient.expireTimerVersion}. Ignoring.")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val mediaMessage = IncomingMessage(
|
val mediaMessage = IncomingMessage(
|
||||||
type = MessageType.EXPIRATION_UPDATE,
|
type = MessageType.EXPIRATION_UPDATE,
|
||||||
from = senderRecipientId,
|
from = senderRecipient.id,
|
||||||
sentTimeMillis = envelope.timestamp!! - if (sideEffect) 1 else 0,
|
sentTimeMillis = envelope.timestamp!! - if (sideEffect) 1 else 0,
|
||||||
serverTimeMillis = envelope.serverTimestamp!!,
|
serverTimeMillis = envelope.serverTimestamp!!,
|
||||||
receivedTimeMillis = receivedTime,
|
receivedTimeMillis = receivedTime,
|
||||||
|
@ -353,7 +359,13 @@ object DataMessageProcessor {
|
||||||
)
|
)
|
||||||
|
|
||||||
val insertResult: InsertResult? = SignalDatabase.messages.insertMessageInbox(mediaMessage, -1).orNull()
|
val insertResult: InsertResult? = SignalDatabase.messages.insertMessageInbox(mediaMessage, -1).orNull()
|
||||||
SignalDatabase.recipients.setExpireMessages(threadRecipientId, expiresIn.inWholeSeconds.toInt())
|
|
||||||
|
if (expireTimerVersion != null) {
|
||||||
|
SignalDatabase.recipients.setExpireMessages(threadRecipientId, expiresIn.inWholeSeconds.toInt(), expireTimerVersion)
|
||||||
|
} else {
|
||||||
|
// TODO [expireVersion] After unsupported builds expire, we can remove this branch
|
||||||
|
SignalDatabase.recipients.setExpireMessagesWithoutIncrementingVersion(threadRecipientId, expiresIn.inWholeSeconds.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
if (insertResult != null) {
|
if (insertResult != null) {
|
||||||
return insertResult
|
return insertResult
|
||||||
|
@ -372,15 +384,16 @@ object DataMessageProcessor {
|
||||||
fun handlePossibleExpirationUpdate(
|
fun handlePossibleExpirationUpdate(
|
||||||
envelope: Envelope,
|
envelope: Envelope,
|
||||||
metadata: EnvelopeMetadata,
|
metadata: EnvelopeMetadata,
|
||||||
senderRecipientId: RecipientId,
|
senderRecipient: Recipient,
|
||||||
threadRecipient: Recipient,
|
threadRecipient: Recipient,
|
||||||
groupId: GroupId.V2?,
|
groupId: GroupId.V2?,
|
||||||
expiresIn: Duration,
|
expiresIn: Duration,
|
||||||
|
expireTimerVersion: Int?,
|
||||||
receivedTime: Long
|
receivedTime: Long
|
||||||
) {
|
) {
|
||||||
if (threadRecipient.expiresInSeconds.toLong() != expiresIn.inWholeSeconds) {
|
if (threadRecipient.expiresInSeconds.toLong() != expiresIn.inWholeSeconds || ((expireTimerVersion ?: -1) > threadRecipient.expireTimerVersion)) {
|
||||||
warn(envelope.timestamp!!, "Message expire time didn't match thread expire time. Handling timer update.")
|
warn(envelope.timestamp!!, "Message expire time didn't match thread expire time. Handling timer update.")
|
||||||
handleExpirationUpdate(envelope, metadata, senderRecipientId, threadRecipient.id, groupId, expiresIn, receivedTime, true)
|
handleExpirationUpdate(envelope, metadata, senderRecipient, threadRecipient.id, groupId, expiresIn, expireTimerVersion, receivedTime, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -741,7 +754,7 @@ object DataMessageProcessor {
|
||||||
threadRecipient = senderRecipient
|
threadRecipient = senderRecipient
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePossibleExpirationUpdate(envelope, metadata, senderRecipient.id, threadRecipient, groupId, message.expireTimerDuration, receivedTime)
|
handlePossibleExpirationUpdate(envelope, metadata, senderRecipient, threadRecipient, groupId, message.expireTimerDuration, message.expireTimerVersion, receivedTime)
|
||||||
|
|
||||||
if (message.hasGroupContext) {
|
if (message.hasGroupContext) {
|
||||||
parentStoryId = GroupReply(storyMessageId.id)
|
parentStoryId = GroupReply(storyMessageId.id)
|
||||||
|
@ -892,7 +905,7 @@ object DataMessageProcessor {
|
||||||
val attachments: List<Attachment> = message.attachments.toPointersWithinLimit()
|
val attachments: List<Attachment> = message.attachments.toPointersWithinLimit()
|
||||||
val messageRanges: BodyRangeList? = if (message.bodyRanges.isNotEmpty()) message.bodyRanges.asSequence().take(BODY_RANGE_PROCESSING_LIMIT).filter { it.mentionAci == null }.toList().toBodyRangeList() else null
|
val messageRanges: BodyRangeList? = if (message.bodyRanges.isNotEmpty()) message.bodyRanges.asSequence().take(BODY_RANGE_PROCESSING_LIMIT).filter { it.mentionAci == null }.toList().toBodyRangeList() else null
|
||||||
|
|
||||||
handlePossibleExpirationUpdate(envelope, metadata, senderRecipient.id, threadRecipient, groupId, message.expireTimerDuration, receivedTime)
|
handlePossibleExpirationUpdate(envelope, metadata, senderRecipient, threadRecipient, groupId, message.expireTimerDuration, message.expireTimerVersion, receivedTime)
|
||||||
|
|
||||||
val mediaMessage = IncomingMessage(
|
val mediaMessage = IncomingMessage(
|
||||||
type = MessageType.NORMAL,
|
type = MessageType.NORMAL,
|
||||||
|
@ -972,7 +985,7 @@ object DataMessageProcessor {
|
||||||
|
|
||||||
val body = message.body ?: ""
|
val body = message.body ?: ""
|
||||||
|
|
||||||
handlePossibleExpirationUpdate(envelope, metadata, senderRecipient.id, threadRecipient, groupId, message.expireTimerDuration, receivedTime)
|
handlePossibleExpirationUpdate(envelope, metadata, senderRecipient, threadRecipient, groupId, message.expireTimerDuration, message.expireTimerVersion, receivedTime)
|
||||||
|
|
||||||
notifyTypingStoppedFromIncomingMessage(context, senderRecipient, threadRecipient.id, metadata.sourceDeviceId)
|
notifyTypingStoppedFromIncomingMessage(context, senderRecipient, threadRecipient.id, metadata.sourceDeviceId)
|
||||||
|
|
||||||
|
|
|
@ -353,6 +353,7 @@ object SyncMessageProcessor {
|
||||||
body = body,
|
body = body,
|
||||||
timestamp = sent.timestamp!!,
|
timestamp = sent.timestamp!!,
|
||||||
expiresIn = targetMessage.expiresIn,
|
expiresIn = targetMessage.expiresIn,
|
||||||
|
expireTimerVersion = targetMessage.expireTimerVersion,
|
||||||
isSecure = true,
|
isSecure = true,
|
||||||
bodyRanges = bodyRanges,
|
bodyRanges = bodyRanges,
|
||||||
messageToEdit = targetMessage.id
|
messageToEdit = targetMessage.id
|
||||||
|
@ -366,6 +367,7 @@ object SyncMessageProcessor {
|
||||||
sentTimeMillis = sent.timestamp!!,
|
sentTimeMillis = sent.timestamp!!,
|
||||||
body = body,
|
body = body,
|
||||||
expiresIn = targetMessage.expiresIn,
|
expiresIn = targetMessage.expiresIn,
|
||||||
|
expireTimerVersion = targetMessage.expireTimerVersion,
|
||||||
isUrgent = true,
|
isUrgent = true,
|
||||||
isSecure = true,
|
isSecure = true,
|
||||||
bodyRanges = bodyRanges,
|
bodyRanges = bodyRanges,
|
||||||
|
@ -429,6 +431,7 @@ object SyncMessageProcessor {
|
||||||
attachments = syncAttachments.ifEmpty { (targetMessage as? MmsMessageRecord)?.slideDeck?.asAttachments() ?: emptyList() },
|
attachments = syncAttachments.ifEmpty { (targetMessage as? MmsMessageRecord)?.slideDeck?.asAttachments() ?: emptyList() },
|
||||||
timestamp = sent.timestamp!!,
|
timestamp = sent.timestamp!!,
|
||||||
expiresIn = targetMessage.expiresIn,
|
expiresIn = targetMessage.expiresIn,
|
||||||
|
expireTimerVersion = targetMessage.expireTimerVersion,
|
||||||
viewOnce = viewOnce,
|
viewOnce = viewOnce,
|
||||||
quote = quote,
|
quote = quote,
|
||||||
contacts = sharedContacts,
|
contacts = sharedContacts,
|
||||||
|
@ -676,13 +679,26 @@ object SyncMessageProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
val recipient: Recipient = getSyncMessageDestination(sent)
|
val recipient: Recipient = getSyncMessageDestination(sent)
|
||||||
val expirationUpdateMessage: OutgoingMessage = OutgoingMessage.expirationUpdateMessage(recipient, if (sideEffect) sent.timestamp!! - 1 else sent.timestamp!!, sent.message!!.expireTimerDuration.inWholeMilliseconds)
|
|
||||||
val threadId: Long = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
|
val threadId: Long = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
|
||||||
val messageId: Long = SignalDatabase.messages.insertMessageOutbox(expirationUpdateMessage, threadId, false, null)
|
val expirationUpdateMessage: OutgoingMessage = OutgoingMessage.expirationUpdateMessage(
|
||||||
|
threadRecipient = recipient,
|
||||||
|
sentTimeMillis = if (sideEffect) sent.timestamp!! - 1 else sent.timestamp!!,
|
||||||
|
expiresIn = sent.message!!.expireTimerDuration.inWholeMilliseconds,
|
||||||
|
expireTimerVersion = sent.message!!.expireTimerVersion ?: 1
|
||||||
|
)
|
||||||
|
|
||||||
SignalDatabase.messages.markAsSent(messageId, true)
|
if (sent.message?.expireTimerVersion == null) {
|
||||||
|
// TODO [expireVersion] After unsupported builds expire, we can remove this branch
|
||||||
SignalDatabase.recipients.setExpireMessages(recipient.id, sent.message!!.expireTimerDuration.inWholeSeconds.toInt())
|
SignalDatabase.recipients.setExpireMessagesWithoutIncrementingVersion(recipient.id, sent.message!!.expireTimerDuration.inWholeSeconds.toInt())
|
||||||
|
val messageId: Long = SignalDatabase.messages.insertMessageOutbox(expirationUpdateMessage, threadId, false, null)
|
||||||
|
SignalDatabase.messages.markAsSent(messageId, true)
|
||||||
|
} else if (sent.message!!.expireTimerVersion!! >= recipient.expireTimerVersion) {
|
||||||
|
SignalDatabase.recipients.setExpireMessages(recipient.id, sent.message!!.expireTimerDuration.inWholeSeconds.toInt(), sent.message!!.expireTimerVersion!!)
|
||||||
|
val messageId: Long = SignalDatabase.messages.insertMessageOutbox(expirationUpdateMessage, threadId, false, null)
|
||||||
|
SignalDatabase.messages.markAsSent(messageId, true)
|
||||||
|
} else {
|
||||||
|
warn(sent.timestamp!!, "[SynchronizeExpiration] Ignoring expire timer update with old version. Received: ${sent.message!!.expireTimerVersion}, Current: ${recipient.expireTimerVersion}")
|
||||||
|
}
|
||||||
|
|
||||||
return threadId
|
return threadId
|
||||||
}
|
}
|
||||||
|
@ -749,7 +765,7 @@ object SyncMessageProcessor {
|
||||||
isSecure = true
|
isSecure = true
|
||||||
)
|
)
|
||||||
|
|
||||||
if (recipient.expiresInSeconds != dataMessage.expireTimerDuration.inWholeSeconds.toInt()) {
|
if (recipient.expiresInSeconds != dataMessage.expireTimerDuration.inWholeSeconds.toInt() || ((dataMessage.expireTimerVersion ?: -1) > recipient.expireTimerVersion)) {
|
||||||
handleSynchronizeSentExpirationUpdate(sent, sideEffect = true)
|
handleSynchronizeSentExpirationUpdate(sent, sideEffect = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -814,7 +830,7 @@ object SyncMessageProcessor {
|
||||||
isSecure = true
|
isSecure = true
|
||||||
)
|
)
|
||||||
|
|
||||||
if (recipient.expiresInSeconds != dataMessage.expireTimerDuration.inWholeSeconds.toInt()) {
|
if (recipient.expiresInSeconds != dataMessage.expireTimerDuration.inWholeSeconds.toInt() || ((dataMessage.expireTimerVersion ?: -1) > recipient.expireTimerVersion)) {
|
||||||
handleSynchronizeSentExpirationUpdate(sent, sideEffect = true)
|
handleSynchronizeSentExpirationUpdate(sent, sideEffect = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -861,7 +877,7 @@ object SyncMessageProcessor {
|
||||||
val expiresInMillis = dataMessage.expireTimerDuration.inWholeMilliseconds
|
val expiresInMillis = dataMessage.expireTimerDuration.inWholeMilliseconds
|
||||||
val bodyRanges = dataMessage.bodyRanges.filter { it.mentionAci == null }.toBodyRangeList()
|
val bodyRanges = dataMessage.bodyRanges.filter { it.mentionAci == null }.toBodyRangeList()
|
||||||
|
|
||||||
if (recipient.expiresInSeconds != dataMessage.expireTimerDuration.inWholeSeconds.toInt()) {
|
if (recipient.expiresInSeconds != dataMessage.expireTimerDuration.inWholeSeconds.toInt() || ((dataMessage.expireTimerVersion ?: -1) > recipient.expireTimerVersion)) {
|
||||||
handleSynchronizeSentExpirationUpdate(sent, sideEffect = true)
|
handleSynchronizeSentExpirationUpdate(sent, sideEffect = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -151,9 +151,10 @@ public class ApplicationMigrations {
|
||||||
static final int CONTACT_LINK_REBUILD = 106;
|
static final int CONTACT_LINK_REBUILD = 106;
|
||||||
static final int DELETE_SYNC_CAPABILITY = 107;
|
static final int DELETE_SYNC_CAPABILITY = 107;
|
||||||
static final int REBUILD_MESSAGE_FTS_INDEX_5 = 108;
|
static final int REBUILD_MESSAGE_FTS_INDEX_5 = 108;
|
||||||
|
static final int EXPIRE_TIMER_CAPABILITY = 109;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final int CURRENT_VERSION = 108;
|
public static final int CURRENT_VERSION = 109;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
|
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
|
||||||
|
@ -688,6 +689,10 @@ public class ApplicationMigrations {
|
||||||
jobs.put(Version.REBUILD_MESSAGE_FTS_INDEX_5, new RebuildMessageSearchIndexMigrationJob());
|
jobs.put(Version.REBUILD_MESSAGE_FTS_INDEX_5, new RebuildMessageSearchIndexMigrationJob());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lastSeenVersion < Version.EXPIRE_TIMER_CAPABILITY) {
|
||||||
|
jobs.put(Version.EXPIRE_TIMER_CAPABILITY, new AttributesMigrationJob());
|
||||||
|
}
|
||||||
|
|
||||||
return jobs;
|
return jobs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ data class OutgoingMessage(
|
||||||
val body: String = "",
|
val body: String = "",
|
||||||
val distributionType: Int = ThreadTable.DistributionTypes.DEFAULT,
|
val distributionType: Int = ThreadTable.DistributionTypes.DEFAULT,
|
||||||
val expiresIn: Long = 0L,
|
val expiresIn: Long = 0L,
|
||||||
|
val expireTimerVersion: Int = threadRecipient.expireTimerVersion,
|
||||||
val isViewOnce: Boolean = false,
|
val isViewOnce: Boolean = false,
|
||||||
val outgoingQuote: QuoteModel? = null,
|
val outgoingQuote: QuoteModel? = null,
|
||||||
val storyType: StoryType = StoryType.NONE,
|
val storyType: StoryType = StoryType.NONE,
|
||||||
|
@ -70,6 +71,7 @@ data class OutgoingMessage(
|
||||||
attachments: List<Attachment> = emptyList(),
|
attachments: List<Attachment> = emptyList(),
|
||||||
timestamp: Long,
|
timestamp: Long,
|
||||||
expiresIn: Long = 0L,
|
expiresIn: Long = 0L,
|
||||||
|
expireTimerVersion: Int = 1,
|
||||||
viewOnce: Boolean = false,
|
viewOnce: Boolean = false,
|
||||||
distributionType: Int = ThreadTable.DistributionTypes.DEFAULT,
|
distributionType: Int = ThreadTable.DistributionTypes.DEFAULT,
|
||||||
storyType: StoryType = StoryType.NONE,
|
storyType: StoryType = StoryType.NONE,
|
||||||
|
@ -92,6 +94,7 @@ data class OutgoingMessage(
|
||||||
attachments = attachments,
|
attachments = attachments,
|
||||||
sentTimeMillis = timestamp,
|
sentTimeMillis = timestamp,
|
||||||
expiresIn = expiresIn,
|
expiresIn = expiresIn,
|
||||||
|
expireTimerVersion = expireTimerVersion,
|
||||||
isViewOnce = viewOnce,
|
isViewOnce = viewOnce,
|
||||||
distributionType = distributionType,
|
distributionType = distributionType,
|
||||||
storyType = storyType,
|
storyType = storyType,
|
||||||
|
@ -119,6 +122,7 @@ data class OutgoingMessage(
|
||||||
body: String? = "",
|
body: String? = "",
|
||||||
timestamp: Long,
|
timestamp: Long,
|
||||||
expiresIn: Long = 0L,
|
expiresIn: Long = 0L,
|
||||||
|
expiresTimerVersion: Int = 1,
|
||||||
viewOnce: Boolean = false,
|
viewOnce: Boolean = false,
|
||||||
storyType: StoryType = StoryType.NONE,
|
storyType: StoryType = StoryType.NONE,
|
||||||
linkPreviews: List<LinkPreview> = emptyList(),
|
linkPreviews: List<LinkPreview> = emptyList(),
|
||||||
|
@ -132,6 +136,7 @@ data class OutgoingMessage(
|
||||||
attachments = slideDeck.asAttachments(),
|
attachments = slideDeck.asAttachments(),
|
||||||
sentTimeMillis = timestamp,
|
sentTimeMillis = timestamp,
|
||||||
expiresIn = expiresIn,
|
expiresIn = expiresIn,
|
||||||
|
expireTimerVersion = expiresTimerVersion,
|
||||||
isViewOnce = viewOnce,
|
isViewOnce = viewOnce,
|
||||||
storyType = storyType,
|
storyType = storyType,
|
||||||
linkPreviews = linkPreviews,
|
linkPreviews = linkPreviews,
|
||||||
|
@ -143,8 +148,8 @@ data class OutgoingMessage(
|
||||||
|
|
||||||
val subscriptionId = -1
|
val subscriptionId = -1
|
||||||
|
|
||||||
fun withExpiry(expiresIn: Long): OutgoingMessage {
|
fun withExpiry(expiresIn: Long, expireTimerVersion: Int): OutgoingMessage {
|
||||||
return copy(expiresIn = expiresIn)
|
return copy(expiresIn = expiresIn, expireTimerVersion = expireTimerVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stripAttachments(): OutgoingMessage {
|
fun stripAttachments(): OutgoingMessage {
|
||||||
|
@ -351,12 +356,13 @@ data class OutgoingMessage(
|
||||||
* Helper for creating expiration update messages.
|
* Helper for creating expiration update messages.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun expirationUpdateMessage(threadRecipient: Recipient, sentTimeMillis: Long, expiresIn: Long): OutgoingMessage {
|
fun expirationUpdateMessage(threadRecipient: Recipient, sentTimeMillis: Long, expiresIn: Long, expireTimerVersion: Int): OutgoingMessage {
|
||||||
return OutgoingMessage(
|
return OutgoingMessage(
|
||||||
threadRecipient = threadRecipient,
|
threadRecipient = threadRecipient,
|
||||||
sentTimeMillis = sentTimeMillis,
|
sentTimeMillis = sentTimeMillis,
|
||||||
expiresIn = expiresIn,
|
expiresIn = expiresIn,
|
||||||
isExpirationUpdate = true,
|
isExpirationUpdate = true,
|
||||||
|
expireTimerVersion = expireTimerVersion,
|
||||||
isUrgent = false,
|
isUrgent = false,
|
||||||
isSecure = true
|
isSecure = true
|
||||||
)
|
)
|
||||||
|
|
|
@ -75,9 +75,10 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
|
||||||
SignalExecutors.BOUNDED.execute(() -> {
|
SignalExecutors.BOUNDED.execute(() -> {
|
||||||
long threadId;
|
long threadId;
|
||||||
|
|
||||||
Recipient recipient = Recipient.resolved(recipientId);
|
Recipient recipient = Recipient.resolved(recipientId);
|
||||||
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.getExpiresInSeconds());
|
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.getExpiresInSeconds());
|
||||||
ParentStoryId parentStoryId = groupStoryId != Long.MIN_VALUE ? ParentStoryId.deserialize(groupStoryId) : null;
|
int expireTimerVersion = recipient.getExpireTimerVersion();
|
||||||
|
ParentStoryId parentStoryId = groupStoryId != Long.MIN_VALUE ? ParentStoryId.deserialize(groupStoryId) : null;
|
||||||
|
|
||||||
switch (replyMethod) {
|
switch (replyMethod) {
|
||||||
case GroupMessage: {
|
case GroupMessage: {
|
||||||
|
@ -86,6 +87,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
|
||||||
new LinkedList<>(),
|
new LinkedList<>(),
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
expiresIn,
|
expiresIn,
|
||||||
|
expireTimerVersion,
|
||||||
false,
|
false,
|
||||||
0,
|
0,
|
||||||
StoryType.NONE,
|
StoryType.NONE,
|
||||||
|
|
|
@ -84,6 +84,7 @@ class Recipient(
|
||||||
private val messageRingtoneUri: Uri? = null,
|
private val messageRingtoneUri: Uri? = null,
|
||||||
private val callRingtoneUri: Uri? = null,
|
private val callRingtoneUri: Uri? = null,
|
||||||
val expiresInSeconds: Int = 0,
|
val expiresInSeconds: Int = 0,
|
||||||
|
val expireTimerVersion: Int = 1,
|
||||||
private val registeredValue: RegisteredState = RegisteredState.UNKNOWN,
|
private val registeredValue: RegisteredState = RegisteredState.UNKNOWN,
|
||||||
val profileKey: ByteArray? = null,
|
val profileKey: ByteArray? = null,
|
||||||
val expiringProfileKeyCredential: ExpiringProfileKeyCredential? = null,
|
val expiringProfileKeyCredential: ExpiringProfileKeyCredential? = null,
|
||||||
|
@ -314,9 +315,12 @@ class Recipient(
|
||||||
/** The notification channel, if both set and supported by the system. Otherwise null. */
|
/** The notification channel, if both set and supported by the system. Otherwise null. */
|
||||||
val notificationChannel: String? = if (!NotificationChannels.supported()) null else notificationChannelValue
|
val notificationChannel: String? = if (!NotificationChannels.supported()) null else notificationChannelValue
|
||||||
|
|
||||||
/** The user's payment capability. */
|
/** The user's capability to handle synchronizing deletes across linked devices. */
|
||||||
val deleteSyncCapability: Capability = capabilities.deleteSync
|
val deleteSyncCapability: Capability = capabilities.deleteSync
|
||||||
|
|
||||||
|
/** The user's capability to handle tracking an expire timer version. */
|
||||||
|
val versionedExpirationTimerCapability: Capability = capabilities.versionedExpirationTimer
|
||||||
|
|
||||||
/** The state around whether we can send sealed sender to this user. */
|
/** The state around whether we can send sealed sender to this user. */
|
||||||
val sealedSenderAccessMode: SealedSenderAccessMode = if (pni.isPresent && pni == serviceId) {
|
val sealedSenderAccessMode: SealedSenderAccessMode = if (pni.isPresent && pni == serviceId) {
|
||||||
SealedSenderAccessMode.DISABLED
|
SealedSenderAccessMode.DISABLED
|
||||||
|
|
|
@ -167,6 +167,7 @@ object RecipientCreator {
|
||||||
callVibrate = record.callVibrateState,
|
callVibrate = record.callVibrateState,
|
||||||
isBlocked = record.isBlocked,
|
isBlocked = record.isBlocked,
|
||||||
expiresInSeconds = record.expireMessages,
|
expiresInSeconds = record.expireMessages,
|
||||||
|
expireTimerVersion = record.expireTimerVersion,
|
||||||
participantIdsValue = participantIds ?: LinkedList(),
|
participantIdsValue = participantIds ?: LinkedList(),
|
||||||
isActiveGroup = groupRecord.map { it.isActive }.orElse(false),
|
isActiveGroup = groupRecord.map { it.isActive }.orElse(false),
|
||||||
profileName = record.signalProfileName,
|
profileName = record.signalProfileName,
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingMessage;
|
import org.thoughtcrime.securesms.mms.OutgoingMessage;
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||||
|
import org.thoughtcrime.securesms.util.ExpirationTimerUtil;
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
|
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
|
||||||
|
@ -324,21 +325,23 @@ public class RecipientUtil {
|
||||||
/**
|
/**
|
||||||
* Checks if a universal timer is set and if the thread should have it set on it. Attempts to abort quickly and perform
|
* Checks if a universal timer is set and if the thread should have it set on it. Attempts to abort quickly and perform
|
||||||
* minimal database access.
|
* minimal database access.
|
||||||
|
*
|
||||||
|
* @return The new expire timer version if the timer was set, otherwise null.
|
||||||
*/
|
*/
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
public static boolean setAndSendUniversalExpireTimerIfNecessary(@NonNull Context context, @NonNull Recipient recipient, long threadId) {
|
public static @Nullable Integer setAndSendUniversalExpireTimerIfNecessary(@NonNull Context context, @NonNull Recipient recipient, long threadId) {
|
||||||
int defaultTimer = SignalStore.settings().getUniversalExpireTimer();
|
int defaultTimer = SignalStore.settings().getUniversalExpireTimer();
|
||||||
if (defaultTimer == 0 || recipient.isGroup() || recipient.isDistributionList() || recipient.getExpiresInSeconds() != 0 || !recipient.isRegistered()) {
|
if (defaultTimer == 0 || recipient.isGroup() || recipient.isDistributionList() || recipient.getExpiresInSeconds() != 0 || !recipient.isRegistered()) {
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (threadId == -1 || SignalDatabase.messages().canSetUniversalTimer(threadId)) {
|
if (threadId == -1 || SignalDatabase.messages().canSetUniversalTimer(threadId)) {
|
||||||
SignalDatabase.recipients().setExpireMessages(recipient.getId(), defaultTimer);
|
int expireTimerVersion = ExpirationTimerUtil.setExpirationTimer(recipient.getId(), defaultTimer);
|
||||||
OutgoingMessage outgoingMessage = OutgoingMessage.expirationUpdateMessage(recipient, System.currentTimeMillis(), defaultTimer * 1000L);
|
OutgoingMessage outgoingMessage = OutgoingMessage.expirationUpdateMessage(recipient, System.currentTimeMillis(), defaultTimer * 1000L, expireTimerVersion);
|
||||||
MessageSender.send(context, outgoingMessage, SignalDatabase.threads().getOrCreateThreadIdFor(recipient), MessageSender.SendType.SIGNAL, null, null);
|
MessageSender.send(context, outgoingMessage, SignalDatabase.threads().getOrCreateThreadIdFor(recipient), MessageSender.SendType.SIGNAL, null, null);
|
||||||
return true;
|
return expireTimerVersion;
|
||||||
}
|
}
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
|
|
|
@ -105,12 +105,13 @@ public final class MultiShareSender {
|
||||||
for (ContactSearchKey.RecipientSearchKey recipientSearchKey : multiShareArgs.getRecipientSearchKeys()) {
|
for (ContactSearchKey.RecipientSearchKey recipientSearchKey : multiShareArgs.getRecipientSearchKeys()) {
|
||||||
Recipient recipient = Recipient.resolved(recipientSearchKey.getRecipientId());
|
Recipient recipient = Recipient.resolved(recipientSearchKey.getRecipientId());
|
||||||
|
|
||||||
long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(recipient);
|
long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(recipient);
|
||||||
List<Mention> mentions = getValidMentionsForRecipient(recipient, multiShareArgs.getMentions());
|
List<Mention> mentions = getValidMentionsForRecipient(recipient, multiShareArgs.getMentions());
|
||||||
MessageSendType sendType = MessageSendType.SignalMessageSendType.INSTANCE;
|
MessageSendType sendType = MessageSendType.SignalMessageSendType.INSTANCE;
|
||||||
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.getExpiresInSeconds());
|
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.getExpiresInSeconds());
|
||||||
List<Contact> contacts = multiShareArgs.getSharedContacts();
|
int expireTimerVersion = recipient.getExpireTimerVersion();
|
||||||
SlideDeck slideDeck = new SlideDeck(primarySlideDeck);
|
List<Contact> contacts = multiShareArgs.getSharedContacts();
|
||||||
|
SlideDeck slideDeck = new SlideDeck(primarySlideDeck);
|
||||||
|
|
||||||
boolean needsSplit = message != null &&
|
boolean needsSplit = message != null &&
|
||||||
message.length() > sendType.calculateCharacters(message).maxPrimaryMessageSize;
|
message.length() > sendType.calculateCharacters(message).maxPrimaryMessageSize;
|
||||||
|
@ -138,6 +139,7 @@ public final class MultiShareSender {
|
||||||
sendType,
|
sendType,
|
||||||
threadId,
|
threadId,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
|
expireTimerVersion,
|
||||||
multiShareArgs.isViewOnce(),
|
multiShareArgs.isViewOnce(),
|
||||||
mentions,
|
mentions,
|
||||||
recipientSearchKey.isStory(),
|
recipientSearchKey.isStory(),
|
||||||
|
@ -182,6 +184,7 @@ public final class MultiShareSender {
|
||||||
@NonNull MessageSendType sendType,
|
@NonNull MessageSendType sendType,
|
||||||
long threadId,
|
long threadId,
|
||||||
long expiresIn,
|
long expiresIn,
|
||||||
|
int expireTimerVersion,
|
||||||
boolean isViewOnce,
|
boolean isViewOnce,
|
||||||
@NonNull List<Mention> validatedMentions,
|
@NonNull List<Mention> validatedMentions,
|
||||||
boolean isStory,
|
boolean isStory,
|
||||||
|
@ -221,6 +224,7 @@ public final class MultiShareSender {
|
||||||
body,
|
body,
|
||||||
sentTimestamps.getMillis(0),
|
sentTimestamps.getMillis(0),
|
||||||
0L,
|
0L,
|
||||||
|
1,
|
||||||
false,
|
false,
|
||||||
storyType.toTextStoryType(),
|
storyType.toTextStoryType(),
|
||||||
buildLinkPreviews(context, multiShareArgs.getLinkPreview()),
|
buildLinkPreviews(context, multiShareArgs.getLinkPreview()),
|
||||||
|
@ -260,6 +264,7 @@ public final class MultiShareSender {
|
||||||
body,
|
body,
|
||||||
sentTimestamps.getMillis(i),
|
sentTimestamps.getMillis(i),
|
||||||
0L,
|
0L,
|
||||||
|
1,
|
||||||
false,
|
false,
|
||||||
storyType,
|
storyType,
|
||||||
Collections.emptyList(),
|
Collections.emptyList(),
|
||||||
|
@ -277,6 +282,7 @@ public final class MultiShareSender {
|
||||||
body,
|
body,
|
||||||
sentTimestamps.getMillis(0),
|
sentTimestamps.getMillis(0),
|
||||||
expiresIn,
|
expiresIn,
|
||||||
|
expireTimerVersion,
|
||||||
isViewOnce,
|
isViewOnce,
|
||||||
StoryType.NONE,
|
StoryType.NONE,
|
||||||
buildLinkPreviews(context, multiShareArgs.getLinkPreview()),
|
buildLinkPreviews(context, multiShareArgs.getLinkPreview()),
|
||||||
|
|
|
@ -513,8 +513,12 @@ public class MessageSender {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @NonNull OutgoingMessage applyUniversalExpireTimerIfNecessary(@NonNull Context context, @NonNull Recipient recipient, @NonNull OutgoingMessage outgoingMessage, long threadId) {
|
private static @NonNull OutgoingMessage applyUniversalExpireTimerIfNecessary(@NonNull Context context, @NonNull Recipient recipient, @NonNull OutgoingMessage outgoingMessage, long threadId) {
|
||||||
if (!outgoingMessage.isExpirationUpdate() && outgoingMessage.getExpiresIn() == 0 && RecipientUtil.setAndSendUniversalExpireTimerIfNecessary(context, recipient, threadId)) {
|
if (!outgoingMessage.isExpirationUpdate() && outgoingMessage.getExpiresIn() == 0) {
|
||||||
return outgoingMessage.withExpiry(TimeUnit.SECONDS.toMillis(SignalStore.settings().getUniversalExpireTimer()));
|
Integer expireTimerVersion = RecipientUtil.setAndSendUniversalExpireTimerIfNecessary(context, recipient, threadId);
|
||||||
|
|
||||||
|
if (expireTimerVersion != null) {
|
||||||
|
return outgoingMessage.withExpiry(TimeUnit.SECONDS.toMillis(SignalStore.settings().getUniversalExpireTimer()), expireTimerVersion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return outgoingMessage;
|
return outgoingMessage;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.util
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exists as a temporary shim to improve the callsites where we'll be setting the expiration timer.
|
||||||
|
*
|
||||||
|
* Until the versions that don't understand expiration timers expire, we'll have to check capabilities before incrementing the version.
|
||||||
|
*
|
||||||
|
* After those old clients expire, we can remove this shim entirely and call the RecipientTable methods directly.
|
||||||
|
*/
|
||||||
|
object ExpirationTimerUtil {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun setExpirationTimer(recipientId: RecipientId, expirationTimeSeconds: Int): Int {
|
||||||
|
val selfCapable = Recipient.self().versionedExpirationTimerCapability == Recipient.Capability.SUPPORTED
|
||||||
|
val recipientCapable = Recipient.resolved(recipientId).let { it.versionedExpirationTimerCapability == Recipient.Capability.SUPPORTED || it.expireTimerVersion > 2 }
|
||||||
|
|
||||||
|
return if (selfCapable && recipientCapable) {
|
||||||
|
SignalDatabase.recipients.setExpireMessagesAndIncrementVersion(recipientId, expirationTimeSeconds)
|
||||||
|
} else {
|
||||||
|
SignalDatabase.recipients.setExpireMessagesWithoutIncrementingVersion(recipientId, expirationTimeSeconds)
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,6 +44,7 @@ object RecipientDatabaseTestUtils {
|
||||||
messageRingtone: Uri = Uri.EMPTY,
|
messageRingtone: Uri = Uri.EMPTY,
|
||||||
callRingtone: Uri = Uri.EMPTY,
|
callRingtone: Uri = Uri.EMPTY,
|
||||||
expireMessages: Int = 0,
|
expireMessages: Int = 0,
|
||||||
|
expireTimerVersion: Int = 1,
|
||||||
registered: RecipientTable.RegisteredState = RecipientTable.RegisteredState.REGISTERED,
|
registered: RecipientTable.RegisteredState = RecipientTable.RegisteredState.REGISTERED,
|
||||||
profileKey: ByteArray = Random.nextBytes(32),
|
profileKey: ByteArray = Random.nextBytes(32),
|
||||||
expiringProfileKeyCredential: ExpiringProfileKeyCredential? = null,
|
expiringProfileKeyCredential: ExpiringProfileKeyCredential? = null,
|
||||||
|
@ -107,6 +108,7 @@ object RecipientDatabaseTestUtils {
|
||||||
messageRingtone = messageRingtone,
|
messageRingtone = messageRingtone,
|
||||||
callRingtone = callRingtone,
|
callRingtone = callRingtone,
|
||||||
expireMessages = expireMessages,
|
expireMessages = expireMessages,
|
||||||
|
expireTimerVersion = expireTimerVersion,
|
||||||
registered = registered,
|
registered = registered,
|
||||||
profileKey = profileKey,
|
profileKey = profileKey,
|
||||||
expiringProfileKeyCredential = expiringProfileKeyCredential,
|
expiringProfileKeyCredential = expiringProfileKeyCredential,
|
||||||
|
@ -124,7 +126,8 @@ object RecipientDatabaseTestUtils {
|
||||||
sealedSenderAccessMode = sealedSenderAccessMode,
|
sealedSenderAccessMode = sealedSenderAccessMode,
|
||||||
capabilities = RecipientRecord.Capabilities(
|
capabilities = RecipientRecord.Capabilities(
|
||||||
rawBits = capabilities,
|
rawBits = capabilities,
|
||||||
deleteSync = Recipient.Capability.deserialize(Bitmask.read(capabilities, RecipientTable.Capabilities.DELETE_SYNC, RecipientTable.Capabilities.BIT_LENGTH).toInt())
|
deleteSync = Recipient.Capability.deserialize(Bitmask.read(capabilities, RecipientTable.Capabilities.DELETE_SYNC, RecipientTable.Capabilities.BIT_LENGTH).toInt()),
|
||||||
|
versionedExpirationTimer = Recipient.Capability.deserialize(Bitmask.read(capabilities, RecipientTable.Capabilities.VERSIONED_EXPIRATION_TIMER, RecipientTable.Capabilities.BIT_LENGTH).toInt())
|
||||||
),
|
),
|
||||||
storageId = storageId,
|
storageId = storageId,
|
||||||
mentionSetting = mentionSetting,
|
mentionSetting = mentionSetting,
|
||||||
|
|
|
@ -20,6 +20,7 @@ object TestMms {
|
||||||
sentTimeMillis: Long = System.currentTimeMillis(),
|
sentTimeMillis: Long = System.currentTimeMillis(),
|
||||||
receivedTimestampMillis: Long = System.currentTimeMillis(),
|
receivedTimestampMillis: Long = System.currentTimeMillis(),
|
||||||
expiresIn: Long = 0,
|
expiresIn: Long = 0,
|
||||||
|
expireTimerVersion: Int = 1,
|
||||||
viewOnce: Boolean = false,
|
viewOnce: Boolean = false,
|
||||||
distributionType: Int = ThreadTable.DistributionTypes.DEFAULT,
|
distributionType: Int = ThreadTable.DistributionTypes.DEFAULT,
|
||||||
type: Long = MessageTypes.BASE_INBOX_TYPE,
|
type: Long = MessageTypes.BASE_INBOX_TYPE,
|
||||||
|
@ -29,23 +30,24 @@ object TestMms {
|
||||||
storyType: StoryType = StoryType.NONE
|
storyType: StoryType = StoryType.NONE
|
||||||
): Long {
|
): Long {
|
||||||
val message = OutgoingMessage(
|
val message = OutgoingMessage(
|
||||||
recipient,
|
recipient = recipient,
|
||||||
body,
|
body = body,
|
||||||
emptyList(),
|
attachments = emptyList(),
|
||||||
sentTimeMillis,
|
timestamp = sentTimeMillis,
|
||||||
expiresIn,
|
expiresIn = expiresIn,
|
||||||
viewOnce,
|
expireTimerVersion = expireTimerVersion,
|
||||||
distributionType,
|
viewOnce = viewOnce,
|
||||||
storyType,
|
distributionType = distributionType,
|
||||||
null,
|
storyType = storyType,
|
||||||
false,
|
parentStoryId = null,
|
||||||
null,
|
isStoryReaction = false,
|
||||||
emptyList(),
|
quote = null,
|
||||||
emptyList(),
|
contacts = emptyList(),
|
||||||
emptyList(),
|
previews = emptyList(),
|
||||||
emptySet(),
|
mentions = emptyList(),
|
||||||
emptySet(),
|
networkFailures = emptySet(),
|
||||||
null
|
mismatches = emptySet(),
|
||||||
|
giftBadge = null
|
||||||
)
|
)
|
||||||
|
|
||||||
return insert(
|
return insert(
|
||||||
|
@ -61,7 +63,7 @@ object TestMms {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun insert(
|
private fun insert(
|
||||||
db: SQLiteDatabase,
|
db: SQLiteDatabase,
|
||||||
message: OutgoingMessage,
|
message: OutgoingMessage,
|
||||||
recipientId: RecipientId = message.threadRecipient.id,
|
recipientId: RecipientId = message.threadRecipient.id,
|
||||||
|
@ -81,6 +83,7 @@ object TestMms {
|
||||||
put(MessageTable.DATE_RECEIVED, receivedTimestampMillis)
|
put(MessageTable.DATE_RECEIVED, receivedTimestampMillis)
|
||||||
put(MessageTable.SMS_SUBSCRIPTION_ID, message.subscriptionId)
|
put(MessageTable.SMS_SUBSCRIPTION_ID, message.subscriptionId)
|
||||||
put(MessageTable.EXPIRES_IN, message.expiresIn)
|
put(MessageTable.EXPIRES_IN, message.expiresIn)
|
||||||
|
put(MessageTable.EXPIRE_TIMER_VERSION, message.expireTimerVersion)
|
||||||
put(MessageTable.VIEW_ONCE, message.isViewOnce)
|
put(MessageTable.VIEW_ONCE, message.isViewOnce)
|
||||||
put(MessageTable.FROM_RECIPIENT_ID, recipientId.serialize())
|
put(MessageTable.FROM_RECIPIENT_ID, recipientId.serialize())
|
||||||
put(MessageTable.TO_RECIPIENT_ID, recipientId.serialize())
|
put(MessageTable.TO_RECIPIENT_ID, recipientId.serialize())
|
||||||
|
|
|
@ -141,6 +141,7 @@ object FakeMessageRecords {
|
||||||
subscriptionId: Int = -1,
|
subscriptionId: Int = -1,
|
||||||
expiresIn: Long = -1,
|
expiresIn: Long = -1,
|
||||||
expireStarted: Long = -1,
|
expireStarted: Long = -1,
|
||||||
|
expireTimerVersion: Int = individualRecipient.expireTimerVersion,
|
||||||
viewOnce: Boolean = false,
|
viewOnce: Boolean = false,
|
||||||
hasReadReceipt: Boolean = false,
|
hasReadReceipt: Boolean = false,
|
||||||
quote: Quote? = null,
|
quote: Quote? = null,
|
||||||
|
@ -178,6 +179,7 @@ object FakeMessageRecords {
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
expireStarted,
|
expireStarted,
|
||||||
|
expireTimerVersion,
|
||||||
viewOnce,
|
viewOnce,
|
||||||
hasReadReceipt,
|
hasReadReceipt,
|
||||||
quote,
|
quote,
|
||||||
|
|
|
@ -1059,6 +1059,7 @@ public class SignalServiceMessageSender {
|
||||||
if (message.getExpiresInSeconds() > 0) {
|
if (message.getExpiresInSeconds() > 0) {
|
||||||
builder.expireTimer(message.getExpiresInSeconds());
|
builder.expireTimer(message.getExpiresInSeconds());
|
||||||
}
|
}
|
||||||
|
builder.expireTimerVersion(message.getExpireTimerVersion());
|
||||||
|
|
||||||
if (message.getProfileKey().isPresent()) {
|
if (message.getProfileKey().isPresent()) {
|
||||||
builder.profileKey(ByteString.of(message.getProfileKey().get()));
|
builder.profileKey(ByteString.of(message.getProfileKey().get()));
|
||||||
|
|
|
@ -55,6 +55,7 @@ class AccountAttributes @JsonCreator constructor(
|
||||||
|
|
||||||
data class Capabilities @JsonCreator constructor(
|
data class Capabilities @JsonCreator constructor(
|
||||||
@JsonProperty val storage: Boolean,
|
@JsonProperty val storage: Boolean,
|
||||||
@JsonProperty val deleteSync: Boolean
|
@JsonProperty val deleteSync: Boolean,
|
||||||
|
@JsonProperty val expireTimerVersion: Boolean
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ class SignalServiceDataMessage private constructor(
|
||||||
val body: Optional<String>,
|
val body: Optional<String>,
|
||||||
val isEndSession: Boolean,
|
val isEndSession: Boolean,
|
||||||
val expiresInSeconds: Int,
|
val expiresInSeconds: Int,
|
||||||
|
val expireTimerVersion: Int,
|
||||||
val isExpirationUpdate: Boolean,
|
val isExpirationUpdate: Boolean,
|
||||||
val profileKey: Optional<ByteArray>,
|
val profileKey: Optional<ByteArray>,
|
||||||
val isProfileKeyUpdate: Boolean,
|
val isProfileKeyUpdate: Boolean,
|
||||||
|
@ -79,6 +80,7 @@ class SignalServiceDataMessage private constructor(
|
||||||
private var body: String? = null
|
private var body: String? = null
|
||||||
private var endSession: Boolean = false
|
private var endSession: Boolean = false
|
||||||
private var expiresInSeconds: Int = 0
|
private var expiresInSeconds: Int = 0
|
||||||
|
private var expireTimerVersion: Int = 1
|
||||||
private var expirationUpdate: Boolean = false
|
private var expirationUpdate: Boolean = false
|
||||||
private var profileKey: ByteArray? = null
|
private var profileKey: ByteArray? = null
|
||||||
private var profileKeyUpdate: Boolean = false
|
private var profileKeyUpdate: Boolean = false
|
||||||
|
@ -133,6 +135,11 @@ class SignalServiceDataMessage private constructor(
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun withExpireTimerVersion(expireTimerVersion: Int): Builder {
|
||||||
|
this.expireTimerVersion = expireTimerVersion
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
fun withProfileKey(profileKey: ByteArray?): Builder {
|
fun withProfileKey(profileKey: ByteArray?): Builder {
|
||||||
this.profileKey = profileKey
|
this.profileKey = profileKey
|
||||||
return this
|
return this
|
||||||
|
@ -225,6 +232,7 @@ class SignalServiceDataMessage private constructor(
|
||||||
body = body.emptyIfStringEmpty(),
|
body = body.emptyIfStringEmpty(),
|
||||||
isEndSession = endSession,
|
isEndSession = endSession,
|
||||||
expiresInSeconds = expiresInSeconds,
|
expiresInSeconds = expiresInSeconds,
|
||||||
|
expireTimerVersion = expireTimerVersion,
|
||||||
isExpirationUpdate = expirationUpdate,
|
isExpirationUpdate = expirationUpdate,
|
||||||
profileKey = profileKey.asOptional(),
|
profileKey = profileKey.asOptional(),
|
||||||
isProfileKeyUpdate = profileKeyUpdate,
|
isProfileKeyUpdate = profileKeyUpdate,
|
||||||
|
|
|
@ -21,6 +21,7 @@ public class DeviceContact {
|
||||||
private final Optional<VerifiedMessage> verified;
|
private final Optional<VerifiedMessage> verified;
|
||||||
private final Optional<ProfileKey> profileKey;
|
private final Optional<ProfileKey> profileKey;
|
||||||
private final Optional<Integer> expirationTimer;
|
private final Optional<Integer> expirationTimer;
|
||||||
|
private final Optional<Integer> expirationTimerVersion;
|
||||||
private final Optional<Integer> inboxPosition;
|
private final Optional<Integer> inboxPosition;
|
||||||
private final boolean archived;
|
private final boolean archived;
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ public class DeviceContact {
|
||||||
Optional<VerifiedMessage> verified,
|
Optional<VerifiedMessage> verified,
|
||||||
Optional<ProfileKey> profileKey,
|
Optional<ProfileKey> profileKey,
|
||||||
Optional<Integer> expirationTimer,
|
Optional<Integer> expirationTimer,
|
||||||
|
Optional<Integer> expirationTimerVersion,
|
||||||
Optional<Integer> inboxPosition,
|
Optional<Integer> inboxPosition,
|
||||||
boolean archived)
|
boolean archived)
|
||||||
{
|
{
|
||||||
|
@ -39,16 +41,17 @@ public class DeviceContact {
|
||||||
throw new IllegalArgumentException("Must have either ACI or E164");
|
throw new IllegalArgumentException("Must have either ACI or E164");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.aci = aci;
|
this.aci = aci;
|
||||||
this.e164 = e164;
|
this.e164 = e164;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.avatar = avatar;
|
this.avatar = avatar;
|
||||||
this.color = color;
|
this.color = color;
|
||||||
this.verified = verified;
|
this.verified = verified;
|
||||||
this.profileKey = profileKey;
|
this.profileKey = profileKey;
|
||||||
this.expirationTimer = expirationTimer;
|
this.expirationTimer = expirationTimer;
|
||||||
this.inboxPosition = inboxPosition;
|
this.expirationTimerVersion = expirationTimerVersion;
|
||||||
this.archived = archived;
|
this.inboxPosition = inboxPosition;
|
||||||
|
this.archived = archived;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<DeviceContactAvatar> getAvatar() {
|
public Optional<DeviceContactAvatar> getAvatar() {
|
||||||
|
@ -83,6 +86,10 @@ public class DeviceContact {
|
||||||
return expirationTimer;
|
return expirationTimer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<Integer> getExpirationTimerVersion() {
|
||||||
|
return expirationTimerVersion;
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<Integer> getInboxPosition() {
|
public Optional<Integer> getInboxPosition() {
|
||||||
return inboxPosition;
|
return inboxPosition;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,16 +47,17 @@ public class DeviceContactsInputStream extends ChunkedInputStream {
|
||||||
throw new IOException("Missing contact address!");
|
throw new IOException("Missing contact address!");
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<ACI> aci = Optional.ofNullable(ACI.parseOrNull(details.aci));
|
Optional<ACI> aci = Optional.ofNullable(ACI.parseOrNull(details.aci));
|
||||||
Optional<String> e164 = Optional.ofNullable(details.number);
|
Optional<String> e164 = Optional.ofNullable(details.number);
|
||||||
Optional<String> name = Optional.ofNullable(details.name);
|
Optional<String> name = Optional.ofNullable(details.name);
|
||||||
Optional<DeviceContactAvatar> avatar = Optional.empty();
|
Optional<DeviceContactAvatar> avatar = Optional.empty();
|
||||||
Optional<String> color = details.color != null ? Optional.of(details.color) : Optional.empty();
|
Optional<String> color = details.color != null ? Optional.of(details.color) : Optional.empty();
|
||||||
Optional<VerifiedMessage> verified = Optional.empty();
|
Optional<VerifiedMessage> verified = Optional.empty();
|
||||||
Optional<ProfileKey> profileKey = Optional.empty();
|
Optional<ProfileKey> profileKey = Optional.empty();
|
||||||
Optional<Integer> expireTimer = Optional.empty();
|
Optional<Integer> expireTimer = Optional.empty();
|
||||||
Optional<Integer> inboxPosition = Optional.empty();
|
Optional<Integer> expireTimerVersion = Optional.empty();
|
||||||
boolean archived = false;
|
Optional<Integer> inboxPosition = Optional.empty();
|
||||||
|
boolean archived = false;
|
||||||
|
|
||||||
if (details.avatar != null && details.avatar.length != null) {
|
if (details.avatar != null && details.avatar.length != null) {
|
||||||
long avatarLength = details.avatar.length;
|
long avatarLength = details.avatar.length;
|
||||||
|
@ -103,13 +104,17 @@ public class DeviceContactsInputStream extends ChunkedInputStream {
|
||||||
expireTimer = Optional.of(details.expireTimer);
|
expireTimer = Optional.of(details.expireTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (details.expireTimerVersion != null && details.expireTimerVersion > 0) {
|
||||||
|
expireTimerVersion = Optional.of(details.expireTimerVersion);
|
||||||
|
}
|
||||||
|
|
||||||
if (details.inboxPosition != null) {
|
if (details.inboxPosition != null) {
|
||||||
inboxPosition = Optional.of(details.inboxPosition);
|
inboxPosition = Optional.of(details.inboxPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
archived = details.archived;
|
archived = details.archived;
|
||||||
|
|
||||||
return new DeviceContact(aci, e164, name, avatar, color, verified, profileKey, expireTimer, inboxPosition, archived);
|
return new DeviceContact(aci, e164, name, avatar, color, verified, profileKey, expireTimer, expireTimerVersion, inboxPosition, archived);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,12 +195,16 @@ public class SignalServiceProfile {
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private boolean deleteSync;
|
private boolean deleteSync;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private boolean versionedExpirationTimer;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
public Capabilities() {}
|
public Capabilities() {}
|
||||||
|
|
||||||
public Capabilities(boolean storage, boolean deleteSync) {
|
public Capabilities(boolean storage, boolean deleteSync, boolean versionedExpirationTimer) {
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.deleteSync = deleteSync;
|
this.deleteSync = deleteSync;
|
||||||
|
this.versionedExpirationTimer = versionedExpirationTimer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isStorage() {
|
public boolean isStorage() {
|
||||||
|
@ -210,6 +214,10 @@ public class SignalServiceProfile {
|
||||||
public boolean isDeleteSync() {
|
public boolean isDeleteSync() {
|
||||||
return deleteSync;
|
return deleteSync;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isVersionedExpirationTimer() {
|
||||||
|
return versionedExpirationTimer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExpiringProfileKeyCredentialResponse getExpiringProfileKeyCredentialResponse() {
|
public ExpiringProfileKeyCredentialResponse getExpiringProfileKeyCredentialResponse() {
|
||||||
|
|
|
@ -332,6 +332,7 @@ message DataMessage {
|
||||||
optional GroupContextV2 groupV2 = 15;
|
optional GroupContextV2 groupV2 = 15;
|
||||||
optional uint32 flags = 4;
|
optional uint32 flags = 4;
|
||||||
optional uint32 expireTimer = 5;
|
optional uint32 expireTimer = 5;
|
||||||
|
optional uint32 expireTimerVersion = 23;
|
||||||
optional bytes profileKey = 6;
|
optional bytes profileKey = 6;
|
||||||
optional uint64 timestamp = 7;
|
optional uint64 timestamp = 7;
|
||||||
optional Quote quote = 8;
|
optional Quote quote = 8;
|
||||||
|
@ -793,17 +794,18 @@ message ContactDetails {
|
||||||
optional uint32 length = 2;
|
optional uint32 length = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional string number = 1;
|
optional string number = 1;
|
||||||
optional string aci = 9;
|
optional string aci = 9;
|
||||||
optional string name = 2;
|
optional string name = 2;
|
||||||
optional Avatar avatar = 3;
|
optional Avatar avatar = 3;
|
||||||
optional string color = 4;
|
optional string color = 4;
|
||||||
optional Verified verified = 5;
|
optional Verified verified = 5;
|
||||||
optional bytes profileKey = 6;
|
optional bytes profileKey = 6;
|
||||||
reserved /*blocked*/ 7;
|
reserved /*blocked*/ 7;
|
||||||
optional uint32 expireTimer = 8;
|
optional uint32 expireTimer = 8;
|
||||||
optional uint32 inboxPosition = 10;
|
optional uint32 expireTimerVersion = 12;
|
||||||
optional bool archived = 11;
|
optional uint32 inboxPosition = 10;
|
||||||
|
optional bool archived = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GroupDetails {
|
message GroupDetails {
|
||||||
|
@ -817,17 +819,17 @@ message GroupDetails {
|
||||||
optional string e164 = 2;
|
optional string e164 = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional bytes id = 1;
|
optional bytes id = 1;
|
||||||
optional string name = 2;
|
optional string name = 2;
|
||||||
repeated string membersE164 = 3;
|
repeated string membersE164 = 3;
|
||||||
repeated Member members = 9;
|
repeated Member members = 9;
|
||||||
optional Avatar avatar = 4;
|
optional Avatar avatar = 4;
|
||||||
optional bool active = 5 [default = true];
|
optional bool active = 5 [default = true];
|
||||||
optional uint32 expireTimer = 6;
|
optional uint32 expireTimer = 6;
|
||||||
optional string color = 7;
|
optional string color = 7;
|
||||||
optional bool blocked = 8;
|
optional bool blocked = 8;
|
||||||
optional uint32 inboxPosition = 10;
|
optional uint32 inboxPosition = 10;
|
||||||
optional bool archived = 11;
|
optional bool archived = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PaymentAddress {
|
message PaymentAddress {
|
||||||
|
|
|
@ -40,6 +40,7 @@ public class DeviceContactsInputStreamTest {
|
||||||
Optional.of(generateProfileKey()),
|
Optional.of(generateProfileKey()),
|
||||||
Optional.of(0),
|
Optional.of(0),
|
||||||
Optional.of(0),
|
Optional.of(0),
|
||||||
|
Optional.of(0),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -53,6 +54,7 @@ public class DeviceContactsInputStreamTest {
|
||||||
Optional.of(generateProfileKey()),
|
Optional.of(generateProfileKey()),
|
||||||
Optional.of(0),
|
Optional.of(0),
|
||||||
Optional.of(0),
|
Optional.of(0),
|
||||||
|
Optional.of(0),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue