mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 09:20:19 +01:00
Add additional delete sync support.
This commit is contained in:
committed by
Greyson Parrelli
parent
d22d18da47
commit
c80ccd70ec
@@ -462,8 +462,11 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
* - Not an encryption message
|
||||
* - Not a report spam message
|
||||
* - Not a message rqeuest accepted message
|
||||
* - Not be a story
|
||||
* - Have a valid sent timestamp
|
||||
* - Be a normal message or direct (1:1) story reply
|
||||
*
|
||||
* Changes should be reflected in [MmsMessageRecord.canDeleteSync].
|
||||
*/
|
||||
private const val IS_ADDRESSABLE_CLAUSE = """
|
||||
(($TYPE & ${MessageTypes.BASE_TYPE_MASK}) = ${MessageTypes.BASE_SENT_TYPE} OR ($TYPE & ${MessageTypes.BASE_TYPE_MASK}) = ${MessageTypes.BASE_INBOX_TYPE}) AND
|
||||
@@ -472,7 +475,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
($TYPE & ${MessageTypes.KEY_EXCHANGE_MASK}) = 0 AND
|
||||
($TYPE & ${MessageTypes.ENCRYPTION_MASK}) = 0 AND
|
||||
($TYPE & ${MessageTypes.SPECIAL_TYPES_MASK}) != ${MessageTypes.SPECIAL_TYPE_REPORTED_SPAM} AND
|
||||
($TYPE & ${MessageTypes.SPECIAL_TYPES_MASK}) != ${MessageTypes.SPECIAL_TYPE_MESSAGE_REQUEST_ACCEPTED} AND
|
||||
($TYPE & ${MessageTypes.SPECIAL_TYPES_MASK}) != ${MessageTypes.SPECIAL_TYPE_MESSAGE_REQUEST_ACCEPTED} AND
|
||||
$STORY_TYPE = 0 AND
|
||||
$DATE_SENT > 0 AND
|
||||
$PARENT_STORY_ID <= 0
|
||||
"""
|
||||
@@ -3277,7 +3281,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
threads.setLastScrolled(threadId, 0)
|
||||
|
||||
val threadDeleted = if (updateThread) {
|
||||
threads.update(threadId, false)
|
||||
threads.update(threadId, unarchive = false, syncThreadDelete = false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -3332,11 +3336,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteThread(threadId: Long) {
|
||||
Log.d(TAG, "deleteThread($threadId)")
|
||||
deleteThreads(setOf(threadId))
|
||||
}
|
||||
|
||||
private fun getSerializedSharedContacts(insertedAttachmentIds: Map<Attachment, AttachmentId>, contacts: List<Contact>): String? {
|
||||
if (contacts.isEmpty()) {
|
||||
return null
|
||||
@@ -3434,27 +3433,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return ids
|
||||
}
|
||||
|
||||
private fun deleteThreads(threadIds: Set<Long>) {
|
||||
Log.d(TAG, "deleteThreads(count: ${threadIds.size})")
|
||||
|
||||
writableDatabase.withinTransaction { db ->
|
||||
SqlUtil.buildCollectionQuery(THREAD_ID, threadIds).forEach { query ->
|
||||
db.select(ID, THREAD_ID)
|
||||
.from(TABLE_NAME)
|
||||
.where(query.where, query.whereArgs)
|
||||
.run()
|
||||
.forEach { cursor ->
|
||||
deleteMessage(cursor.requireLong(ID), cursor.requireLong(THREAD_ID), notify = false, updateThread = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notifyConversationListeners(threadIds)
|
||||
notifyStickerListeners()
|
||||
notifyStickerPackListeners()
|
||||
OptimizeMessageSearchIndexJob.enqueue()
|
||||
}
|
||||
|
||||
fun deleteMessagesInThreadBeforeDate(threadId: Long, date: Long, inclusive: Boolean): Int {
|
||||
val condition = if (inclusive) "<=" else "<"
|
||||
|
||||
|
||||
@@ -410,6 +410,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
fun maskCapabilitiesToLong(capabilities: SignalServiceProfile.Capabilities): Long {
|
||||
var value: Long = 0
|
||||
value = Bitmask.update(value, Capabilities.PAYMENT_ACTIVATION, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isPaymentActivation).serialize().toLong())
|
||||
value = Bitmask.update(value, Capabilities.DELETE_SYNC, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isDeleteSync).serialize().toLong())
|
||||
return value
|
||||
}
|
||||
}
|
||||
@@ -4577,6 +4578,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
// const val GIFT_BADGES = 6
|
||||
// const val PNP = 7
|
||||
const val PAYMENT_ACTIVATION = 8
|
||||
const val DELETE_SYNC = 9
|
||||
}
|
||||
|
||||
enum class VibrateState(val id: Int) {
|
||||
|
||||
@@ -175,7 +175,8 @@ object RecipientTableCursorUtil {
|
||||
val capabilities = cursor.requireLong(RecipientTable.CAPABILITIES)
|
||||
return RecipientRecord.Capabilities(
|
||||
rawBits = capabilities,
|
||||
paymentActivation = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.PAYMENT_ACTIVATION, Capabilities.BIT_LENGTH).toInt())
|
||||
paymentActivation = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.PAYMENT_ACTIVATION, Capabilities.BIT_LENGTH).toInt()),
|
||||
deleteSync = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.DELETE_SYNC, Capabilities.BIT_LENGTH).toInt())
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,6 @@ import org.thoughtcrime.securesms.util.ConversationUtil
|
||||
import org.thoughtcrime.securesms.util.JsonUtils
|
||||
import org.thoughtcrime.securesms.util.JsonUtils.SaneJSONObject
|
||||
import org.thoughtcrime.securesms.util.LRUCache
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.isScheduled
|
||||
import org.whispersystems.signalservice.api.storage.SignalAccountRecord
|
||||
@@ -326,7 +325,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
return
|
||||
}
|
||||
|
||||
val syncThreadTrimDeletes = SignalStore.settings().shouldSyncThreadTrimDeletes() && RemoteConfig.deleteSyncEnabled
|
||||
val syncThreadTrimDeletes = SignalStore.settings().shouldSyncThreadTrimDeletes() && Recipient.self().deleteSyncCapability.isSupported
|
||||
val threadTrimsToSync = mutableListOf<Pair<Long, Set<MessageRecord>>>()
|
||||
|
||||
readableDatabase
|
||||
@@ -1119,57 +1118,34 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
if (containsAddressable || isEmpty) {
|
||||
false
|
||||
} else {
|
||||
deleteConversation(threadId, syncThreadDeletes = false)
|
||||
deleteConversation(threadId, syncThreadDelete = false)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun deleteConversation(threadId: Long, syncThreadDeletes: Boolean = true) {
|
||||
val recipientIdForThreadId = getRecipientIdForThreadId(threadId)
|
||||
|
||||
var addressableMessages: Set<MessageRecord> = emptySet()
|
||||
writableDatabase.withinTransaction { db ->
|
||||
if (syncThreadDeletes && RemoteConfig.deleteSyncEnabled) {
|
||||
addressableMessages = messages.getMostRecentAddressableMessages(threadId)
|
||||
}
|
||||
|
||||
messages.deleteThread(threadId)
|
||||
drafts.clearDrafts(threadId)
|
||||
db.deactivateThread(threadId)
|
||||
synchronized(threadIdCache) {
|
||||
threadIdCache.remove(recipientIdForThreadId)
|
||||
}
|
||||
}
|
||||
|
||||
if (syncThreadDeletes) {
|
||||
MultiDeviceDeleteSendSyncJob.enqueueThreadDeletes(listOf(threadId to addressableMessages), isFullDelete = true)
|
||||
}
|
||||
|
||||
notifyConversationListListeners()
|
||||
notifyConversationListeners(threadId)
|
||||
AppDependencies.databaseObserver.notifyConversationDeleteListeners(threadId)
|
||||
ConversationUtil.clearShortcuts(context, setOf(recipientIdForThreadId))
|
||||
fun deleteConversation(threadId: Long, syncThreadDelete: Boolean = true) {
|
||||
deleteConversations(setOf(threadId), syncThreadDelete)
|
||||
}
|
||||
|
||||
fun deleteConversations(selectedConversations: Set<Long>) {
|
||||
fun deleteConversations(selectedConversations: Set<Long>, syncThreadDeletes: Boolean = true) {
|
||||
val recipientIds = getRecipientIdsForThreadIds(selectedConversations)
|
||||
|
||||
val addressableMessages = mutableListOf<Pair<Long, Set<MessageRecord>>>()
|
||||
|
||||
val queries: List<SqlUtil.Query> = SqlUtil.buildCollectionQuery(ID, selectedConversations)
|
||||
writableDatabase.withinTransaction { db ->
|
||||
for (query in queries) {
|
||||
db.deactivateThread(query)
|
||||
}
|
||||
|
||||
if (RemoteConfig.deleteSyncEnabled) {
|
||||
if (syncThreadDeletes && Recipient.self().deleteSyncCapability.isSupported) {
|
||||
for (threadId in selectedConversations) {
|
||||
addressableMessages += threadId to messages.getMostRecentAddressableMessages(threadId)
|
||||
}
|
||||
}
|
||||
|
||||
for (query in queries) {
|
||||
db.deactivateThread(query)
|
||||
}
|
||||
|
||||
messages.deleteAbandonedMessages()
|
||||
attachments.trimAllAbandonedAttachments()
|
||||
groupReceipts.deleteAbandonedRows()
|
||||
@@ -1183,12 +1159,19 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
}
|
||||
}
|
||||
|
||||
MultiDeviceDeleteSendSyncJob.enqueueThreadDeletes(addressableMessages, isFullDelete = true)
|
||||
if (syncThreadDeletes) {
|
||||
MultiDeviceDeleteSendSyncJob.enqueueThreadDeletes(addressableMessages, isFullDelete = true)
|
||||
}
|
||||
|
||||
notifyConversationListListeners()
|
||||
notifyConversationListeners(selectedConversations)
|
||||
notifyStickerListeners()
|
||||
notifyStickerPackListeners()
|
||||
AppDependencies.databaseObserver.notifyConversationDeleteListeners(selectedConversations)
|
||||
|
||||
ConversationUtil.clearShortcuts(context, recipientIds)
|
||||
|
||||
OptimizeMessageSearchIndexJob.enqueue()
|
||||
}
|
||||
|
||||
@SuppressLint("DiscouragedApi")
|
||||
@@ -1485,12 +1468,13 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
.run()
|
||||
}
|
||||
|
||||
fun update(threadId: Long, unarchive: Boolean): Boolean {
|
||||
fun update(threadId: Long, unarchive: Boolean, syncThreadDelete: Boolean = true): Boolean {
|
||||
return update(
|
||||
threadId = threadId,
|
||||
unarchive = unarchive,
|
||||
allowDeletion = true,
|
||||
notifyListeners = true
|
||||
notifyListeners = true,
|
||||
syncThreadDelete = syncThreadDelete
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1499,16 +1483,18 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
threadId = threadId,
|
||||
unarchive = unarchive,
|
||||
allowDeletion = true,
|
||||
notifyListeners = false
|
||||
notifyListeners = false,
|
||||
syncThreadDelete = true
|
||||
)
|
||||
}
|
||||
|
||||
fun update(threadId: Long, unarchive: Boolean, allowDeletion: Boolean): Boolean {
|
||||
fun update(threadId: Long, unarchive: Boolean, allowDeletion: Boolean, syncThreadDelete: Boolean = true): Boolean {
|
||||
return update(
|
||||
threadId = threadId,
|
||||
unarchive = unarchive,
|
||||
allowDeletion = allowDeletion,
|
||||
notifyListeners = true
|
||||
notifyListeners = true,
|
||||
syncThreadDelete = syncThreadDelete
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1543,7 +1529,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
stopwatch?.split("thread-update")
|
||||
}
|
||||
|
||||
private fun update(threadId: Long, unarchive: Boolean, allowDeletion: Boolean, notifyListeners: Boolean): Boolean {
|
||||
private fun update(threadId: Long, unarchive: Boolean, allowDeletion: Boolean, notifyListeners: Boolean, syncThreadDelete: Boolean): Boolean {
|
||||
if (threadId == -1L) {
|
||||
Log.d(TAG, "Skipping update for threadId -1")
|
||||
return false
|
||||
@@ -1558,7 +1544,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
if (!meaningfulMessages) {
|
||||
if (shouldDelete) {
|
||||
Log.d(TAG, "Deleting thread $threadId because it has no meaningful messages.")
|
||||
deleteConversation(threadId)
|
||||
deleteConversation(threadId, syncThreadDelete = syncThreadDelete)
|
||||
return@withinTransaction true
|
||||
} else if (!isPinned) {
|
||||
return@withinTransaction false
|
||||
|
||||
@@ -823,6 +823,13 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
return revisionNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* A message that can be correctly identified and delete sync'd across devices.
|
||||
*/
|
||||
public boolean canDeleteSync() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static final class InviteAddState {
|
||||
|
||||
private final boolean invited;
|
||||
|
||||
@@ -308,6 +308,19 @@ public class MmsMessageRecord extends MessageRecord {
|
||||
return latestRevisionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDeleteSync() {
|
||||
return (isSent() || MessageTypes.isInboxType(type)) &&
|
||||
(isSecure() || isPush()) &&
|
||||
(type & MessageTypes.GROUP_MASK) == 0 &&
|
||||
(type & MessageTypes.KEY_EXCHANGE_MASK) == 0 &&
|
||||
!isReportedSpam() &&
|
||||
!isMessageRequestAccepted() &&
|
||||
storyType == StoryType.NONE &&
|
||||
getDateSent() > 0 &&
|
||||
(parentStoryId == null || parentStoryId.isDirectReply());
|
||||
}
|
||||
|
||||
public @NonNull MmsMessageRecord withReactions(@NonNull List<ReactionRecord> reactions) {
|
||||
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
|
||||
|
||||
@@ -119,12 +119,14 @@ data class RecipientRecord(
|
||||
|
||||
data class Capabilities(
|
||||
val rawBits: Long,
|
||||
val paymentActivation: Recipient.Capability
|
||||
val paymentActivation: Recipient.Capability,
|
||||
val deleteSync: Recipient.Capability
|
||||
) {
|
||||
companion object {
|
||||
@JvmField
|
||||
val UNKNOWN = Capabilities(
|
||||
0,
|
||||
Recipient.Capability.UNKNOWN,
|
||||
Recipient.Capability.UNKNOWN
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user