From fb2a332513a37bbde9d0666c548e18c6b9702baa Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Mon, 29 Jul 2024 10:26:19 -0400 Subject: [PATCH] Update to latest Backup.proto. --- .../securesms/backup/v2/ImportExportTest.kt | 102 +++--------------- .../securesms/backup/v2/BackupRepository.kt | 34 +++--- .../v2/database/CallTableBackupExtensions.kt | 6 +- .../v2/database/ChatItemExportIterator.kt | 4 +- .../v2/database/ChatItemImportInserter.kt | 38 +++---- .../DistributionListTablesBackupExtensions.kt | 6 +- .../database/MessageTableBackupExtensions.kt | 6 +- .../database/ThreadTableBackupExtensions.kt | 85 +++++++-------- .../v2/processor/AccountDataProcessor.kt | 67 +++++++++++- .../v2/processor/AdHocCallBackupProcessor.kt | 6 +- .../v2/processor/ChatBackupProcessor.kt | 16 ++- .../v2/processor/ChatItemBackupProcessor.kt | 6 +- .../v2/processor/RecipientBackupProcessor.kt | 20 ++-- .../conversation/colors/ChatColors.kt | 4 +- .../model/GroupsV2UpdateMessageConverter.kt | 2 +- .../model/GroupsV2UpdateMessageProducer.java | 2 +- app/src/main/protowire/Backup.proto | 34 ++++-- 17 files changed, 211 insertions(+), 227 deletions(-) diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ImportExportTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ImportExportTest.kt index 8bda88b174..8b0c43a01b 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ImportExportTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ImportExportTest.kt @@ -128,7 +128,7 @@ class ImportExportTest { linkPreviews = true, notDiscoverableByPhoneNumber = true, preferContactAvatars = true, - universalExpireTimer = 42, + universalExpireTimerSeconds = 42, displayBadgesOnProfile = true, keepMutedChatsArchived = true, hasSetMyStoriesPrivacy = true, @@ -556,46 +556,6 @@ class ImportExportTest { ) } - @Test - fun deletedDistributionList() { - val alexa = Recipient( - id = 4, - contact = Contact( - aci = TestRecipientUtils.nextAci().toByteString(), - pni = TestRecipientUtils.nextPni().toByteString(), - username = "cool.01", - e164 = 141255501234, - blocked = true, - visibility = Contact.Visibility.HIDDEN, - registered = Contact.Registered(), - profileKey = TestRecipientUtils.generateProfileKey().toByteString(), - profileSharing = true, - profileGivenName = "Alexa", - profileFamilyName = "Kim", - hideStory = true - ) - ) - val importData = exportFrames( - *standardFrames, - alexa, - Recipient( - id = 6, - distributionList = DistributionListItem( - distributionId = DistributionId.create().asUuid().toByteArray().toByteString(), - deletionTimestamp = 12345L - ) - ) - ) - import(importData) - val exported = BackupRepository.debugExport() - val expected = exportFrames( - *standardFrames, - alexa - ) - - compare(expected, exported) - } - @Test fun chatThreads() { importExport( @@ -1254,12 +1214,7 @@ class ImportExportTest { chatId = 1, authorId = alice.id, dateSent = dateSentStart++, - incoming = ChatItem.IncomingMessageDetails( - dateReceived = dateSentStart, - dateServerSent = dateSentStart, - read = true, - sealedSender = true - ), + directionless = ChatItem.DirectionlessMessageDetails(), updateMessage = ChatUpdateMessage( simpleUpdate = SimpleChatUpdate( type = SimpleChatUpdate.Type.fromValue(i)!! @@ -1287,12 +1242,7 @@ class ImportExportTest { chatId = 1, authorId = alice.id, dateSent = dateSentStart++, - incoming = ChatItem.IncomingMessageDetails( - dateReceived = dateSentStart, - dateServerSent = dateSentStart, - read = true, - sealedSender = true - ), + directionless = ChatItem.DirectionlessMessageDetails(), updateMessage = ChatUpdateMessage( expirationTimerChange = ExpirationTimerChatUpdate( 1000 @@ -1303,11 +1253,7 @@ class ImportExportTest { chatId = 1, authorId = selfRecipient.id, dateSent = dateSentStart++, - outgoing = ChatItem.OutgoingMessageDetails( - sendStatus = listOf( - SendStatus(alice.id, deliveryStatus = SendStatus.Status.READ, sealedSender = true, lastStatusUpdateTimestamp = -1) - ) - ), + directionless = ChatItem.DirectionlessMessageDetails(), updateMessage = ChatUpdateMessage( expirationTimerChange = ExpirationTimerChatUpdate( 0 @@ -1318,9 +1264,7 @@ class ImportExportTest { chatId = 1, authorId = selfRecipient.id, dateSent = dateSentStart++, - outgoing = ChatItem.OutgoingMessageDetails( - sendStatus = listOf(SendStatus(alice.id, deliveryStatus = SendStatus.Status.READ, sealedSender = true, lastStatusUpdateTimestamp = -1)) - ), + directionless = ChatItem.DirectionlessMessageDetails(), updateMessage = ChatUpdateMessage( expirationTimerChange = ExpirationTimerChatUpdate( 10000 @@ -1331,12 +1275,7 @@ class ImportExportTest { chatId = 1, authorId = alice.id, dateSent = dateSentStart++, - incoming = ChatItem.IncomingMessageDetails( - dateReceived = dateSentStart, - dateServerSent = dateSentStart, - read = true, - sealedSender = true - ), + directionless = ChatItem.DirectionlessMessageDetails(), updateMessage = ChatUpdateMessage( expirationTimerChange = ExpirationTimerChatUpdate( 0 @@ -1348,7 +1287,6 @@ class ImportExportTest { @Test fun profileChangeChatUpdateMessage() { - var dateSentStart = 100L importExport( *standardFrames, alice, @@ -1356,13 +1294,8 @@ class ImportExportTest { ChatItem( chatId = 1, authorId = alice.id, - dateSent = dateSentStart++, - incoming = ChatItem.IncomingMessageDetails( - dateReceived = dateSentStart, - dateServerSent = dateSentStart, - read = true, - sealedSender = true - ), + dateSent = 100L, + directionless = ChatItem.DirectionlessMessageDetails(), updateMessage = ChatUpdateMessage( profileChange = ProfileChangeChatUpdate( previousName = "Aliceee Kim", @@ -1375,7 +1308,6 @@ class ImportExportTest { @Test fun threadMergeChatUpdate() { - var dateSentStart = 100L importExport( *standardFrames, alice, @@ -1383,13 +1315,8 @@ class ImportExportTest { ChatItem( chatId = 1, authorId = alice.id, - dateSent = dateSentStart++, - incoming = ChatItem.IncomingMessageDetails( - dateReceived = dateSentStart, - dateServerSent = dateSentStart, - read = true, - sealedSender = true - ), + dateSent = 100L, + directionless = ChatItem.DirectionlessMessageDetails(), updateMessage = ChatUpdateMessage( threadMerge = ThreadMergeChatUpdate( previousE164 = 141255501237 @@ -1409,13 +1336,8 @@ class ImportExportTest { ChatItem( chatId = 1, authorId = alice.id, - dateSent = dateSentStart++, - incoming = ChatItem.IncomingMessageDetails( - dateReceived = dateSentStart, - dateServerSent = dateSentStart, - read = true, - sealedSender = true - ), + dateSent = dateSentStart, + directionless = ChatItem.DirectionlessMessageDetails(), updateMessage = ChatUpdateMessage( sessionSwitchover = SessionSwitchoverChatUpdate( e164 = 141255501237 diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt index d78263f58e..f4aeb2ccb3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt @@ -220,8 +220,8 @@ object BackupRepository { backupTimeMs = exportState.backupTime ) ) - // Note: Without a transaction, we may export inconsistent state. But because we have a transaction, - // writes from other threads are blocked. This is something to think more about. + + // We're using a snapshot, so the transaction is more for perf than correctness dbSnapshot.rawWritableDatabase.withinTransaction { AccountDataProcessor.export(dbSnapshot, signalStoreSnapshot) { writer.write(it) @@ -316,29 +316,29 @@ object BackupRepository { SignalDatabase.recipients.setProfileSharing(selfId, true) eventTimer.emit("setup") - val backupState = BackupState(backupKey) - val chatItemInserter: ChatItemImportInserter = ChatItemBackupProcessor.beginImport(backupState) + val importState = ImportState(backupKey) + val chatItemInserter: ChatItemImportInserter = ChatItemBackupProcessor.beginImport(importState) val totalLength = frameReader.getStreamLength() for (frame in frameReader) { when { frame.account != null -> { - AccountDataProcessor.import(frame.account, selfId) + AccountDataProcessor.import(frame.account, selfId, importState) eventTimer.emit("account") } frame.recipient != null -> { - RecipientBackupProcessor.import(frame.recipient, backupState) + RecipientBackupProcessor.import(frame.recipient, importState) eventTimer.emit("recipient") } frame.chat != null -> { - ChatBackupProcessor.import(frame.chat, backupState) + ChatBackupProcessor.import(frame.chat, importState) eventTimer.emit("chat") } frame.adHocCall != null -> { - AdHocCallBackupProcessor.import(frame.adHocCall, backupState) + AdHocCallBackupProcessor.import(frame.adHocCall, importState) eventTimer.emit("call") } @@ -362,7 +362,7 @@ object BackupRepository { eventTimer.emit("chatItem") } - backupState.chatIdToLocalThreadId.values.forEach { + importState.chatIdToLocalThreadId.values.forEach { SignalDatabase.threads.update(it, unarchive = false, allowDeletion = false) } } @@ -947,15 +947,17 @@ data class ArchivedMediaObject(val mediaId: String, val cdn: Int) data class BackupDirectories(val backupDir: String, val mediaDir: String) class ExportState(val backupTime: Long, val allowMediaBackup: Boolean) { - val recipientIds = HashSet() - val threadIds = HashSet() + val recipientIds: MutableSet = hashSetOf() + val threadIds: MutableSet = hashSetOf() + val localToRemoteCustomChatColors: MutableMap = hashMapOf() } -class BackupState(val backupKey: BackupKey) { - val backupToLocalRecipientId = HashMap() - val chatIdToLocalThreadId = HashMap() - val chatIdToLocalRecipientId = HashMap() - val chatIdToBackupRecipientId = HashMap() +class ImportState(val backupKey: BackupKey) { + val remoteToLocalRecipientId: MutableMap = hashMapOf() + val chatIdToLocalThreadId: MutableMap = hashMapOf() + val chatIdToLocalRecipientId: MutableMap = hashMapOf() + val chatIdToBackupRecipientId: MutableMap = hashMapOf() + val remoteToLocalColorId: MutableMap = hashMapOf() } class BackupMetadata( diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallTableBackupExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallTableBackupExtensions.kt index defe7c8cec..b3ddd898e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallTableBackupExtensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallTableBackupExtensions.kt @@ -10,7 +10,7 @@ import android.database.sqlite.SQLiteDatabase import androidx.core.content.contentValuesOf import org.signal.core.util.requireLong import org.signal.core.util.select -import org.thoughtcrime.securesms.backup.v2.BackupState +import org.thoughtcrime.securesms.backup.v2.ImportState import org.thoughtcrime.securesms.backup.v2.proto.AdHocCall import org.thoughtcrime.securesms.database.CallTable import org.thoughtcrime.securesms.database.RecipientTable @@ -26,7 +26,7 @@ fun CallTable.getAdhocCallsForBackup(): CallLogIterator { ) } -fun CallTable.restoreCallLogFromBackup(call: AdHocCall, backupState: BackupState) { +fun CallTable.restoreCallLogFromBackup(call: AdHocCall, importState: ImportState) { val event = when (call.state) { AdHocCall.State.GENERIC -> CallTable.Event.GENERIC_GROUP_CALL AdHocCall.State.UNKNOWN_STATE -> CallTable.Event.GENERIC_GROUP_CALL @@ -34,7 +34,7 @@ fun CallTable.restoreCallLogFromBackup(call: AdHocCall, backupState: BackupState val values = contentValuesOf( CallTable.CALL_ID to call.callId, - CallTable.PEER to backupState.backupToLocalRecipientId[call.recipientId]!!.serialize(), + CallTable.PEER to importState.remoteToLocalRecipientId[call.recipientId]!!.serialize(), CallTable.TYPE to CallTable.Type.serialize(CallTable.Type.AD_HOC_CALL), CallTable.DIRECTION to CallTable.Direction.serialize(CallTable.Direction.OUTGOING), CallTable.EVENT to CallTable.Event.serialize(event), diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ChatItemExportIterator.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ChatItemExportIterator.kt index e8e35bbfc6..26470d9a71 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ChatItemExportIterator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ChatItemExportIterator.kt @@ -183,7 +183,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize: builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.REPORTED_SPAM) } MessageTypes.isExpirationTimerUpdate(record.type) -> { - builder.updateMessage = ChatUpdateMessage(expirationTimerChange = ExpirationTimerChatUpdate(record.expiresIn.toInt())) + builder.updateMessage = ChatUpdateMessage(expirationTimerChange = ExpirationTimerChatUpdate(record.expiresIn)) builder.expiresInMs = 0 } MessageTypes.isProfileChange(record.type) -> { @@ -774,7 +774,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize: mediaName = archiveMediaName ?: this.getMediaName().toString(), cdnNumber = if (archiveMediaName != null) archiveCdn else Cdn.CDN_3.cdnNumber, // TODO (clark): Update when new proto with optional cdn is landed key = Base64.decode(remoteKey).toByteString(), - size = this.size.toInt(), + size = this.size, digest = remoteDigest.toByteString() ) } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ChatItemImportInserter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ChatItemImportInserter.kt index 3240522eb1..f2d6193874 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ChatItemImportInserter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ChatItemImportInserter.kt @@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.attachments.Attachment import org.thoughtcrime.securesms.attachments.Cdn import org.thoughtcrime.securesms.attachments.PointerAttachment import org.thoughtcrime.securesms.attachments.TombstoneAttachment -import org.thoughtcrime.securesms.backup.v2.BackupState +import org.thoughtcrime.securesms.backup.v2.ImportState import org.thoughtcrime.securesms.backup.v2.proto.BodyRange import org.thoughtcrime.securesms.backup.v2.proto.ChatItem import org.thoughtcrime.securesms.backup.v2.proto.ChatUpdateMessage @@ -89,7 +89,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.GiftBadge as BackupGiftBadge */ class ChatItemImportInserter( private val db: SQLiteDatabase, - private val backupState: BackupState, + private val importState: ImportState, private val batchSize: Int ) { companion object { @@ -155,25 +155,25 @@ class ChatItemImportInserter( * If this item causes the buffer to hit the batch size, then a batch of items will actually be inserted. */ fun insert(chatItem: ChatItem) { - val fromLocalRecipientId: RecipientId? = backupState.backupToLocalRecipientId[chatItem.authorId] + val fromLocalRecipientId: RecipientId? = importState.remoteToLocalRecipientId[chatItem.authorId] if (fromLocalRecipientId == null) { Log.w(TAG, "[insert] Could not find a local recipient for backup recipient ID ${chatItem.authorId}! Skipping.") return } - val chatLocalRecipientId: RecipientId? = backupState.chatIdToLocalRecipientId[chatItem.chatId] + val chatLocalRecipientId: RecipientId? = importState.chatIdToLocalRecipientId[chatItem.chatId] if (chatLocalRecipientId == null) { Log.w(TAG, "[insert] Could not find a local recipient for chatId ${chatItem.chatId}! Skipping.") return } - val localThreadId: Long? = backupState.chatIdToLocalThreadId[chatItem.chatId] + val localThreadId: Long? = importState.chatIdToLocalThreadId[chatItem.chatId] if (localThreadId == null) { Log.w(TAG, "[insert] Could not find a local threadId for backup chatId ${chatItem.chatId}! Skipping.") return } - val chatBackupRecipientId: Long? = backupState.chatIdToBackupRecipientId[chatItem.chatId] + val chatBackupRecipientId: Long? = importState.chatIdToBackupRecipientId[chatItem.chatId] if (chatBackupRecipientId == null) { Log.w(TAG, "[insert] Could not find a backup recipientId for backup chatId ${chatItem.chatId}! Skipping.") return @@ -283,7 +283,7 @@ class ChatItemImportInserter( CallTable.MESSAGE_ID to messageRowId, CallTable.PEER to chatRecipientId.serialize(), CallTable.TYPE to CallTable.Type.serialize(CallTable.Type.GROUP_CALL), - CallTable.DIRECTION to CallTable.Direction.serialize(if (backupState.backupToLocalRecipientId[updateMessage.groupCall.ringerRecipientId] == selfId) CallTable.Direction.OUTGOING else CallTable.Direction.INCOMING), + CallTable.DIRECTION to CallTable.Direction.serialize(if (importState.remoteToLocalRecipientId[updateMessage.groupCall.ringerRecipientId] == selfId) CallTable.Direction.OUTGOING else CallTable.Direction.INCOMING), CallTable.EVENT to CallTable.Event.serialize( when (updateMessage.groupCall.state) { GroupCall.State.ACCEPTED -> CallTable.Event.ACCEPTED @@ -460,8 +460,8 @@ class ChatItemImportInserter( contentValues.put(MessageTable.UNIDENTIFIED, this.outgoing.sendStatus.count { it.sealedSender }) contentValues.put(MessageTable.READ, 1) - contentValues.addNetworkFailures(this, backupState) - contentValues.addIdentityKeyMismatches(this, backupState) + contentValues.addNetworkFailures(this, importState) + contentValues.addIdentityKeyMismatches(this, importState) } else { contentValues.put(MessageTable.VIEWED_COLUMN, 0) contentValues.put(MessageTable.HAS_READ_RECEIPT, 0) @@ -529,7 +529,7 @@ class ChatItemImportInserter( return reactions .mapNotNull { - val authorId: Long? = backupState.backupToLocalRecipientId[it.authorId]?.toLong() + val authorId: Long? = importState.remoteToLocalRecipientId[it.authorId]?.toLong() if (authorId != null) { contentValuesOf( @@ -557,7 +557,7 @@ class ChatItemImportInserter( } return this.outgoing.sendStatus.mapNotNull { sendStatus -> - val recipientId = backupState.backupToLocalRecipientId[sendStatus.recipientId] + val recipientId = importState.remoteToLocalRecipientId[sendStatus.recipientId] if (recipientId != null) { contentValuesOf( @@ -674,7 +674,7 @@ class ChatItemImportInserter( } updateMessage.groupCall != null -> { val startedCallRecipientId = if (updateMessage.groupCall.startedCallRecipientId != null) { - backupState.backupToLocalRecipientId[updateMessage.groupCall.startedCallRecipientId] + importState.remoteToLocalRecipientId[updateMessage.groupCall.startedCallRecipientId] } else { null } @@ -815,7 +815,7 @@ class ChatItemImportInserter( private fun ContentValues.addQuote(quote: Quote) { this.put(MessageTable.QUOTE_ID, quote.targetSentTimestamp ?: MessageTable.QUOTE_TARGET_MISSING_ID) - this.put(MessageTable.QUOTE_AUTHOR, backupState.backupToLocalRecipientId[quote.authorId]!!.serialize()) + this.put(MessageTable.QUOTE_AUTHOR, importState.remoteToLocalRecipientId[quote.authorId]!!.serialize()) this.put(MessageTable.QUOTE_BODY, quote.text) this.put(MessageTable.QUOTE_TYPE, quote.type.toLocalQuoteType()) this.put(MessageTable.QUOTE_BODY_RANGES, quote.bodyRanges.toLocalBodyRanges()?.encode()) @@ -840,14 +840,14 @@ class ChatItemImportInserter( } } - private fun ContentValues.addNetworkFailures(chatItem: ChatItem, backupState: BackupState) { + private fun ContentValues.addNetworkFailures(chatItem: ChatItem, importState: ImportState) { if (chatItem.outgoing == null) { return } val networkFailures = chatItem.outgoing.sendStatus .filter { status -> status.networkFailure } - .mapNotNull { status -> backupState.backupToLocalRecipientId[status.recipientId] } + .mapNotNull { status -> importState.remoteToLocalRecipientId[status.recipientId] } .map { recipientId -> NetworkFailure(recipientId) } .toSet() @@ -856,14 +856,14 @@ class ChatItemImportInserter( } } - private fun ContentValues.addIdentityKeyMismatches(chatItem: ChatItem, backupState: BackupState) { + private fun ContentValues.addIdentityKeyMismatches(chatItem: ChatItem, importState: ImportState) { if (chatItem.outgoing == null) { return } val mismatches = chatItem.outgoing.sendStatus .filter { status -> status.identityKeyMismatch } - .mapNotNull { status -> backupState.backupToLocalRecipientId[status.recipientId] } + .mapNotNull { status -> importState.remoteToLocalRecipientId[status.recipientId] } .map { recipientId -> IdentityKeyMismatch(recipientId, null) } // TODO We probably want the actual identity key in this status situation? .toSet() @@ -965,8 +965,8 @@ class ChatItemImportInserter( cdnKey = backupLocator.transitCdnKey, archiveCdn = backupLocator.cdnNumber, archiveMediaName = backupLocator.mediaName, - archiveMediaId = backupState.backupKey.deriveMediaId(MediaName(backupLocator.mediaName)).encode(), - archiveThumbnailMediaId = backupState.backupKey.deriveMediaId(MediaName.forThumbnailFromMediaName(backupLocator.mediaName)).encode(), + archiveMediaId = importState.backupKey.deriveMediaId(MediaName(backupLocator.mediaName)).encode(), + archiveThumbnailMediaId = importState.backupKey.deriveMediaId(MediaName.forThumbnailFromMediaName(backupLocator.mediaName)).encode(), digest = backupLocator.digest.toByteArray(), incrementalMac = incrementalMac?.toByteArray(), incrementalMacChunkSize = incrementalMacChunkSize, diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/DistributionListTablesBackupExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/DistributionListTablesBackupExtensions.kt index e5809e5f88..76aea11833 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/DistributionListTablesBackupExtensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/DistributionListTablesBackupExtensions.kt @@ -15,7 +15,7 @@ import org.signal.core.util.requireNonNullString import org.signal.core.util.requireObject import org.signal.core.util.select import org.signal.core.util.withinTransaction -import org.thoughtcrime.securesms.backup.v2.BackupState +import org.thoughtcrime.securesms.backup.v2.ImportState import org.thoughtcrime.securesms.backup.v2.proto.DistributionList import org.thoughtcrime.securesms.backup.v2.proto.DistributionListItem import org.thoughtcrime.securesms.database.DistributionListTables @@ -98,7 +98,7 @@ fun DistributionListTables.getMembersForBackup(id: DistributionListId): List 0) { val dlistId = createList( name = "", @@ -115,7 +115,7 @@ fun DistributionListTables.restoreFromBackup(dlistItem: DistributionListItem, ba val dlist = dlistItem.distributionList ?: return null val members: List = dlist.memberRecipientIds - .mapNotNull { backupState.backupToLocalRecipientId[it] } + .mapNotNull { importState.remoteToLocalRecipientId[it] } if (members.size != dlist.memberRecipientIds.size) { Log.w(TAG, "Couldn't find some member recipients! Missing backup recipientIds: ${dlist.memberRecipientIds.toSet() - members.toSet()}") diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/MessageTableBackupExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/MessageTableBackupExtensions.kt index 122727b78c..40735c4dbd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/MessageTableBackupExtensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/MessageTableBackupExtensions.kt @@ -8,7 +8,7 @@ package org.thoughtcrime.securesms.backup.v2.database import org.signal.core.util.SqlUtil import org.signal.core.util.logging.Log import org.signal.core.util.select -import org.thoughtcrime.securesms.backup.v2.BackupState +import org.thoughtcrime.securesms.backup.v2.ImportState import org.thoughtcrime.securesms.database.MessageTable import org.thoughtcrime.securesms.database.MessageTypes import java.util.concurrent.TimeUnit @@ -69,8 +69,8 @@ fun MessageTable.getMessagesForBackup(backupTime: Long, archiveMedia: Boolean): return ChatItemExportIterator(cursor, 100, archiveMedia) } -fun MessageTable.createChatItemInserter(backupState: BackupState): ChatItemImportInserter { - return ChatItemImportInserter(writableDatabase, backupState, 100) +fun MessageTable.createChatItemInserter(importState: ImportState): ChatItemImportInserter { + return ChatItemImportInserter(writableDatabase, importState, 100) } fun MessageTable.clearAllDataForBackupRestore() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ThreadTableBackupExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ThreadTableBackupExtensions.kt index d0c1c96fe7..36db382bcc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ThreadTableBackupExtensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ThreadTableBackupExtensions.kt @@ -16,12 +16,12 @@ import org.signal.core.util.requireBoolean import org.signal.core.util.requireInt import org.signal.core.util.requireLong import org.signal.core.util.toInt +import org.thoughtcrime.securesms.backup.v2.ImportState import org.thoughtcrime.securesms.backup.v2.proto.Chat import org.thoughtcrime.securesms.backup.v2.proto.ChatStyle import org.thoughtcrime.securesms.conversation.colors.ChatColors import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette import org.thoughtcrime.securesms.database.RecipientTable -import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.ThreadTable import org.thoughtcrime.securesms.database.model.databaseprotos.ChatColor import org.thoughtcrime.securesms.recipients.RecipientId @@ -29,7 +29,7 @@ import java.io.Closeable private val TAG = Log.tag(ThreadTable::class.java) -fun ThreadTable.getThreadsForBackup(): ChatIterator { +fun ThreadTable.getThreadsForBackup(): ChatExportIterator { //language=sql val query = """ SELECT @@ -49,7 +49,7 @@ fun ThreadTable.getThreadsForBackup(): ChatIterator { """ val cursor = readableDatabase.query(query) - return ChatIterator(cursor) + return ChatExportIterator(cursor) } fun ThreadTable.clearAllDataForBackupRestore() { @@ -58,15 +58,10 @@ fun ThreadTable.clearAllDataForBackupRestore() { clearCache() } -fun ThreadTable.restoreFromBackup(chat: Chat, recipientId: RecipientId): Long? { - val chatColor = chat.style?.parseChatColor() - val chatColorWithId = if (chatColor != null && chatColor.id is ChatColors.Id.NotSet) { - val savedColors = SignalDatabase.chatColors.getSavedChatColors() - val match = savedColors.find { it.matchesWithoutId(chatColor) } - match ?: SignalDatabase.chatColors.saveChatColors(chatColor) - } else { - chatColor - } +fun ThreadTable.restoreFromBackup(chat: Chat, recipientId: RecipientId, importState: ImportState): Long? { + val chatColor = chat.style?.remoteToLocalChatColors(importState) + + // TODO [backup] Wallpaper val threadId = writableDatabase .insertInto(ThreadTable.TABLE_NAME) @@ -85,8 +80,8 @@ fun ThreadTable.restoreFromBackup(chat: Chat, recipientId: RecipientId): Long? { RecipientTable.MENTION_SETTING to (if (chat.dontNotifyForMentionsIfMuted) RecipientTable.MentionSetting.DO_NOT_NOTIFY.id else RecipientTable.MentionSetting.ALWAYS_NOTIFY.id), RecipientTable.MUTE_UNTIL to chat.muteUntilMs, RecipientTable.MESSAGE_EXPIRATION_TIME to chat.expirationTimerMs, - RecipientTable.CHAT_COLORS to chatColorWithId?.serialize()?.encode(), - RecipientTable.CUSTOM_CHAT_COLORS_ID to (chatColorWithId?.id ?: ChatColors.Id.NotSet).longValue + RecipientTable.CHAT_COLORS to chatColor?.serialize()?.encode(), + RecipientTable.CUSTOM_CHAT_COLORS_ID to (chatColor?.id ?: ChatColors.Id.NotSet).longValue ), "${RecipientTable.ID} = ?", SqlUtil.buildArgs(recipientId.toLong()) @@ -95,7 +90,7 @@ fun ThreadTable.restoreFromBackup(chat: Chat, recipientId: RecipientId): Long? { return threadId } -class ChatIterator(private val cursor: Cursor) : Iterator, Closeable { +class ChatExportIterator(private val cursor: Cursor) : Iterator, Closeable { override fun hasNext(): Boolean { return cursor.count > 0 && !cursor.isLast } @@ -106,31 +101,32 @@ class ChatIterator(private val cursor: Cursor) : Iterator, Closeable { } val serializedChatColors = cursor.requireBlob(RecipientTable.CHAT_COLORS) - val customChatColorsId = ChatColors.Id.forLongValue(cursor.requireLong(RecipientTable.CUSTOM_CHAT_COLORS_ID)) - val chatColors: ChatColors? = if (serializedChatColors != null) { + val chatColorId = ChatColors.Id.forLongValue(cursor.requireLong(RecipientTable.CUSTOM_CHAT_COLORS_ID)) + val chatColors: ChatColors? = serializedChatColors?.let { serialized -> try { - ChatColors.forChatColor(customChatColorsId, ChatColor.ADAPTER.decode(serializedChatColors)) + ChatColors.forChatColor(chatColorId, ChatColor.ADAPTER.decode(serialized)) } catch (e: InvalidProtocolBufferException) { null } - } else { - null } var chatStyleBuilder: ChatStyle.Builder? = null if (chatColors != null) { chatStyleBuilder = ChatStyle.Builder() - val presetBubbleColor = chatColors.tryToMapToBackupPreset() - if (presetBubbleColor != null) { - chatStyleBuilder.bubbleColorPreset = presetBubbleColor - } else if (chatColors.isGradient()) { - chatStyleBuilder.bubbleGradient = ChatStyle.Gradient(angle = chatColors.getDegrees().toInt(), colors = chatColors.getColors().toList()) - } else if (customChatColorsId is ChatColors.Id.Auto) { - chatStyleBuilder.autoBubbleColor = ChatStyle.AutomaticBubbleColor() - } else { - chatStyleBuilder.bubbleSolidColor = chatColors.asSingleColor() + when (chatColorId) { + ChatColors.Id.NotSet -> {} + ChatColors.Id.Auto -> { + chatStyleBuilder.autoBubbleColor = ChatStyle.AutomaticBubbleColor() + } + ChatColors.Id.BuiltIn -> { + chatStyleBuilder.bubbleColorPreset = chatColors.localToRemoteChatColors() + } + is ChatColors.Id.Custom -> { + chatStyleBuilder.customColorId = chatColorId.longValue + } } } + // TODO [backup] wallpaper return Chat( id = cursor.requireLong(ThreadTable.ID), @@ -150,9 +146,9 @@ class ChatIterator(private val cursor: Cursor) : Iterator, Closeable { } } -private fun ChatStyle.parseChatColor(): ChatColors? { - if (bubbleColorPreset != null) { - return when (bubbleColorPreset) { +private fun ChatStyle.remoteToLocalChatColors(importState: ImportState): ChatColors? { + if (this.bubbleColorPreset != null) { + return when (this.bubbleColorPreset) { ChatStyle.BubbleColorPreset.SOLID_CRIMSON -> ChatColorsPalette.Bubbles.CRIMSON ChatStyle.BubbleColorPreset.SOLID_VERMILION -> ChatColorsPalette.Bubbles.VERMILION ChatStyle.BubbleColorPreset.SOLID_BURLAP -> ChatColorsPalette.Bubbles.BURLAP @@ -177,27 +173,22 @@ private fun ChatStyle.parseChatColor(): ChatColors? { ChatStyle.BubbleColorPreset.UNKNOWN_BUBBLE_COLOR_PRESET, ChatStyle.BubbleColorPreset.SOLID_ULTRAMARINE -> ChatColorsPalette.Bubbles.ULTRAMARINE } } - if (autoBubbleColor != null) { + + if (this.autoBubbleColor != null) { return ChatColorsPalette.Bubbles.default.withId(ChatColors.Id.Auto) } - if (bubbleSolidColor != null) { - return ChatColors(id = ChatColors.Id.NotSet, singleColor = bubbleSolidColor, linearGradient = null) - } - if (bubbleGradient != null) { - return ChatColors( - id = ChatColors.Id.NotSet, - singleColor = null, - linearGradient = ChatColors.LinearGradient( - degrees = bubbleGradient.angle.toFloat(), - colors = bubbleGradient.colors.toIntArray(), - positions = floatArrayOf(0f, 1f) - ) - ) + + if (this.customColorId != null) { + return importState.remoteToLocalColorId[this.customColorId]?.let { localId -> + val colorId = ChatColors.Id.forLongValue(localId) + ChatColorsPalette.Bubbles.default.withId(colorId) + } } + return null } -private fun ChatColors.tryToMapToBackupPreset(): ChatStyle.BubbleColorPreset? { +private fun ChatColors.localToRemoteChatColors(): ChatStyle.BubbleColorPreset? { when (this) { // Solids ChatColorsPalette.Bubbles.CRIMSON -> return ChatStyle.BubbleColorPreset.SOLID_CRIMSON diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AccountDataProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AccountDataProcessor.kt index 1e5c7b9002..4199693aa2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AccountDataProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AccountDataProcessor.kt @@ -7,12 +7,16 @@ package org.thoughtcrime.securesms.backup.v2.processor import okio.ByteString.Companion.EMPTY import okio.ByteString.Companion.toByteString +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.backup.v2.ImportState import org.thoughtcrime.securesms.backup.v2.database.restoreSelfFromBackup import org.thoughtcrime.securesms.backup.v2.proto.AccountData +import org.thoughtcrime.securesms.backup.v2.proto.ChatStyle import org.thoughtcrime.securesms.backup.v2.proto.Frame import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository import org.thoughtcrime.securesms.components.settings.app.usernamelinks.UsernameQrCodeColorScheme +import org.thoughtcrime.securesms.conversation.colors.ChatColors import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord import org.thoughtcrime.securesms.dependencies.AppDependencies @@ -32,6 +36,8 @@ import java.util.Currency object AccountDataProcessor { + private val TAG = Log.tag(AccountDataProcessor::class) + fun export(db: SignalDatabase, signalStore: SignalStore, emitter: BackupFrameEmitter) { val context = AppDependencies.application @@ -53,7 +59,7 @@ object AccountDataProcessor { AccountData.UsernameLink( entropy = signalStore.accountValues.usernameLink?.entropy?.toByteString() ?: EMPTY, serverId = signalStore.accountValues.usernameLink?.serverId?.toByteArray()?.toByteString() ?: EMPTY, - color = signalStore.miscValues.usernameQrCodeColorScheme.toBackupUsernameColor() ?: AccountData.UsernameLink.Color.BLUE + color = signalStore.miscValues.usernameQrCodeColorScheme.toBackupUsernameColor() ) } else { null @@ -67,7 +73,7 @@ object AccountDataProcessor { notDiscoverableByPhoneNumber = signalStore.phoneNumberPrivacyValues.phoneNumberDiscoverabilityMode == PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE, phoneNumberSharingMode = signalStore.phoneNumberPrivacyValues.phoneNumberSharingMode.toBackupPhoneNumberSharingMode(), preferContactAvatars = signalStore.settingsValues.isPreferSystemContactPhotos, - universalExpireTimer = signalStore.settingsValues.universalExpireTimer, + universalExpireTimerSeconds = signalStore.settingsValues.universalExpireTimer, preferredReactionEmoji = signalStore.emojiValues.rawReactions, storiesDisabled = signalStore.storyValues.isFeatureDisabled, hasViewedOnboardingStory = signalStore.storyValues.userHasViewedOnboardingStory, @@ -75,7 +81,8 @@ object AccountDataProcessor { keepMutedChatsArchived = signalStore.settingsValues.shouldKeepMutedChatsArchived(), displayBadgesOnProfile = signalStore.inAppPaymentValues.getDisplayBadgesOnProfile(), hasSeenGroupStoryEducationSheet = signalStore.storyValues.userHasSeenGroupStoryEducationSheet, - hasCompletedUsernameOnboarding = signalStore.uiHintValues.hasCompletedUsernameOnboarding() + hasCompletedUsernameOnboarding = signalStore.uiHintValues.hasCompletedUsernameOnboarding(), + customChatColors = db.chatColorsTable.getSavedChatColors().toRemoteChatColors() ), donationSubscriberData = donationSubscriber?.toSubscriberData(signalStore.inAppPaymentValues.isDonationSubscriptionManuallyCancelled()) ) @@ -83,7 +90,7 @@ object AccountDataProcessor { ) } - fun import(accountData: AccountData, selfId: RecipientId) { + fun import(accountData: AccountData, selfId: RecipientId, importState: ImportState) { SignalDatabase.recipients.restoreSelfFromBackup(accountData, selfId) SignalStore.account.setRegistered(true) @@ -99,7 +106,7 @@ object AccountDataProcessor { SignalStore.phoneNumberPrivacy.phoneNumberDiscoverabilityMode = if (settings.notDiscoverableByPhoneNumber) PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE else PhoneNumberDiscoverabilityMode.DISCOVERABLE SignalStore.phoneNumberPrivacy.phoneNumberSharingMode = settings.phoneNumberSharingMode.toLocalPhoneNumberMode() SignalStore.settings.isPreferSystemContactPhotos = settings.preferContactAvatars - SignalStore.settings.universalExpireTimer = settings.universalExpireTimer + SignalStore.settings.universalExpireTimer = settings.universalExpireTimerSeconds SignalStore.emoji.reactions = settings.preferredReactionEmoji SignalStore.inAppPayments.setDisplayBadgesOnProfile(settings.displayBadgesOnProfile) SignalStore.settings.setKeepMutedChatsArchived(settings.keepMutedChatsArchived) @@ -109,6 +116,32 @@ object AccountDataProcessor { SignalStore.story.userHasSeenGroupStoryEducationSheet = settings.hasSeenGroupStoryEducationSheet SignalStore.story.viewedReceiptsEnabled = settings.storyViewReceiptsEnabled ?: settings.readReceipts + settings.customChatColors + .mapNotNull { chatColor -> + val id = ChatColors.Id.forLongValue(chatColor.id) + when { + chatColor.solid != null -> { + ChatColors.forColor(id, chatColor.solid) + } + chatColor.gradient != null -> { + ChatColors.forGradient( + id, + ChatColors.LinearGradient( + degrees = chatColor.gradient.angle.toFloat(), + colors = chatColor.gradient.colors.toIntArray(), + positions = chatColor.gradient.positions.toFloatArray() + ) + ) + } + else -> null + } + } + .forEach { chatColor -> + // We need to use the "NotSet" chatId so that this operation is treated as an insert rather than an update + val saved = SignalDatabase.chatColors.saveChatColors(chatColor.withId(ChatColors.Id.NotSet)) + importState.remoteToLocalColorId[chatColor.id.longValue] = saved.id.longValue + } + if (accountData.donationSubscriberData != null) { if (accountData.donationSubscriberData.subscriberId.size > 0) { val remoteSubscriberId = SubscriberId.fromBytes(accountData.donationSubscriberData.subscriberId.toByteArray()) @@ -204,4 +237,28 @@ object AccountDataProcessor { val currencyCode = currency.currencyCode return AccountData.SubscriberData(subscriberId = subscriberId, currencyCode = currencyCode, manuallyCancelled = manuallyCancelled) } + + private fun List.toRemoteChatColors(): List { + return this + .mapNotNull { local -> + if (local.linearGradient != null) { + ChatStyle.CustomChatColor( + id = local.id.longValue, + gradient = ChatStyle.Gradient( + angle = local.linearGradient.degrees.toInt(), + colors = local.linearGradient.colors.toList(), + positions = local.linearGradient.positions.toList() + ) + ) + } else if (local.singleColor != null) { + ChatStyle.CustomChatColor( + id = local.id.longValue, + solid = local.singleColor + ) + } else { + Log.w(TAG, "Invalid custom color (id = ${local.id}, no gradient or solid color!") + null + } + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AdHocCallBackupProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AdHocCallBackupProcessor.kt index a4243261cf..4748362407 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AdHocCallBackupProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AdHocCallBackupProcessor.kt @@ -6,7 +6,7 @@ package org.thoughtcrime.securesms.backup.v2.processor import org.signal.core.util.logging.Log -import org.thoughtcrime.securesms.backup.v2.BackupState +import org.thoughtcrime.securesms.backup.v2.ImportState import org.thoughtcrime.securesms.backup.v2.database.getAdhocCallsForBackup import org.thoughtcrime.securesms.backup.v2.database.restoreCallLogFromBackup import org.thoughtcrime.securesms.backup.v2.proto.AdHocCall @@ -28,7 +28,7 @@ object AdHocCallBackupProcessor { } } - fun import(call: AdHocCall, backupState: BackupState) { - SignalDatabase.calls.restoreCallLogFromBackup(call, backupState) + fun import(call: AdHocCall, importState: ImportState) { + SignalDatabase.calls.restoreCallLogFromBackup(call, importState) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatBackupProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatBackupProcessor.kt index 88c59a5af5..c3d8492b25 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatBackupProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatBackupProcessor.kt @@ -6,8 +6,8 @@ package org.thoughtcrime.securesms.backup.v2.processor import org.signal.core.util.logging.Log -import org.thoughtcrime.securesms.backup.v2.BackupState import org.thoughtcrime.securesms.backup.v2.ExportState +import org.thoughtcrime.securesms.backup.v2.ImportState import org.thoughtcrime.securesms.backup.v2.database.getThreadsForBackup import org.thoughtcrime.securesms.backup.v2.database.restoreFromBackup import org.thoughtcrime.securesms.backup.v2.proto.Chat @@ -32,19 +32,17 @@ object ChatBackupProcessor { } } - fun import(chat: Chat, backupState: BackupState) { - val recipientId: RecipientId? = backupState.backupToLocalRecipientId[chat.recipientId] + fun import(chat: Chat, importState: ImportState) { + val recipientId: RecipientId? = importState.remoteToLocalRecipientId[chat.recipientId] if (recipientId == null) { Log.w(TAG, "Missing recipient for chat ${chat.id}") return } - SignalDatabase.threads.restoreFromBackup(chat, recipientId)?.let { threadId -> - backupState.chatIdToLocalRecipientId[chat.id] = recipientId - backupState.chatIdToLocalThreadId[chat.id] = threadId - backupState.chatIdToBackupRecipientId[chat.id] = chat.recipientId + SignalDatabase.threads.restoreFromBackup(chat, recipientId, importState)?.let { threadId -> + importState.chatIdToLocalRecipientId[chat.id] = recipientId + importState.chatIdToLocalThreadId[chat.id] = threadId + importState.chatIdToBackupRecipientId[chat.id] = chat.recipientId } - - // TODO there's several fields in the chat that actually need to be restored on the recipient table } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatItemBackupProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatItemBackupProcessor.kt index 3d70c14783..6860488935 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatItemBackupProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatItemBackupProcessor.kt @@ -6,8 +6,8 @@ package org.thoughtcrime.securesms.backup.v2.processor import org.signal.core.util.logging.Log -import org.thoughtcrime.securesms.backup.v2.BackupState import org.thoughtcrime.securesms.backup.v2.ExportState +import org.thoughtcrime.securesms.backup.v2.ImportState import org.thoughtcrime.securesms.backup.v2.database.ChatItemImportInserter import org.thoughtcrime.securesms.backup.v2.database.createChatItemInserter import org.thoughtcrime.securesms.backup.v2.database.getMessagesForBackup @@ -31,7 +31,7 @@ object ChatItemBackupProcessor { } } - fun beginImport(backupState: BackupState): ChatItemImportInserter { - return SignalDatabase.messages.createChatItemInserter(backupState) + fun beginImport(importState: ImportState): ChatItemImportInserter { + return SignalDatabase.messages.createChatItemInserter(importState) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/RecipientBackupProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/RecipientBackupProcessor.kt index 97b31d9331..0f225fae10 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/RecipientBackupProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/RecipientBackupProcessor.kt @@ -6,8 +6,8 @@ package org.thoughtcrime.securesms.backup.v2.processor import org.signal.core.util.logging.Log -import org.thoughtcrime.securesms.backup.v2.BackupState import org.thoughtcrime.securesms.backup.v2.ExportState +import org.thoughtcrime.securesms.backup.v2.ImportState import org.thoughtcrime.securesms.backup.v2.database.BackupRecipient import org.thoughtcrime.securesms.backup.v2.database.getAllForBackup import org.thoughtcrime.securesms.backup.v2.database.getCallLinksForBackup @@ -28,11 +28,11 @@ object RecipientBackupProcessor { val TAG = Log.tag(RecipientBackupProcessor::class.java) - fun export(db: SignalDatabase, signalStore: SignalStore, state: ExportState, emitter: BackupFrameEmitter) { + fun export(db: SignalDatabase, signalStore: SignalStore, exportState: ExportState, emitter: BackupFrameEmitter) { val selfId = db.recipientTable.getByAci(signalStore.accountValues.aci!!).get().toLong() val releaseChannelId = signalStore.releaseChannelValues.releaseChannelRecipientId if (releaseChannelId != null) { - state.recipientIds.add(releaseChannelId.toLong()) + exportState.recipientIds.add(releaseChannelId.toLong()) emitter.emit( Frame( recipient = BackupRecipient( @@ -46,7 +46,7 @@ object RecipientBackupProcessor { db.recipientTable.getContactsForBackup(selfId).use { reader -> for (backupRecipient in reader) { if (backupRecipient != null) { - state.recipientIds.add(backupRecipient.id) + exportState.recipientIds.add(backupRecipient.id) emitter.emit(Frame(recipient = backupRecipient)) } } @@ -54,27 +54,27 @@ object RecipientBackupProcessor { db.recipientTable.getGroupsForBackup().use { reader -> for (backupRecipient in reader) { - state.recipientIds.add(backupRecipient.id) + exportState.recipientIds.add(backupRecipient.id) emitter.emit(Frame(recipient = backupRecipient)) } } db.distributionListTables.getAllForBackup().forEach { - state.recipientIds.add(it.id) + exportState.recipientIds.add(it.id) emitter.emit(Frame(recipient = it)) } db.callLinkTable.getCallLinksForBackup().forEach { - state.recipientIds.add(it.id) + exportState.recipientIds.add(it.id) emitter.emit(Frame(recipient = it)) } } - fun import(recipient: BackupRecipient, backupState: BackupState) { + fun import(recipient: BackupRecipient, importState: ImportState) { val newId = when { recipient.contact != null -> SignalDatabase.recipients.restoreContactFromBackup(recipient.contact) recipient.group != null -> SignalDatabase.recipients.restoreGroupFromBackup(recipient.group) - recipient.distributionList != null -> SignalDatabase.distributionLists.restoreFromBackup(recipient.distributionList, backupState) + recipient.distributionList != null -> SignalDatabase.distributionLists.restoreFromBackup(recipient.distributionList, importState) recipient.self != null -> Recipient.self().id recipient.releaseNotes != null -> SignalDatabase.recipients.restoreReleaseNotes() recipient.callLink != null -> SignalDatabase.callLinks.restoreFromBackup(recipient.callLink) @@ -84,7 +84,7 @@ object RecipientBackupProcessor { } } if (newId != null) { - backupState.backupToLocalRecipientId[recipient.id] = newId + importState.remoteToLocalRecipientId[recipient.id] = newId } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ChatColors.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ChatColors.kt index 1004ff9300..fc4fa18f95 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ChatColors.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ChatColors.kt @@ -28,8 +28,8 @@ import kotlin.math.min @Parcelize class ChatColors( val id: Id, - private val linearGradient: LinearGradient?, - private val singleColor: Int? + val linearGradient: LinearGradient?, + val singleColor: Int? ) : Parcelable { fun isGradient(): Boolean = linearGradient != null diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageConverter.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageConverter.kt index fba1a8f4a5..154357a19d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageConverter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageConverter.kt @@ -384,7 +384,7 @@ object GroupsV2UpdateMessageConverter { updates.add( GroupChangeChatUpdate.Update( groupExpirationTimerUpdate = GroupExpirationTimerUpdate( - expiresInMs = (change.newTimer!!.duration * 1000L).toUInt().toInt(), + expiresInMs = (change.newTimer!!.duration * 1000L).toUInt().toLong(), updaterAci = if (editorUnknown) null else change.editorServiceIdBytes ) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java index dd47adc0e3..47d2214b80 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducer.java @@ -234,7 +234,7 @@ final class GroupsV2UpdateMessageProducer { } } private void describeGroupExpirationTimerUpdate(@NonNull GroupExpirationTimerUpdate update, @NonNull List updates) { - final int duration = Math.toIntExact(Integer.toUnsignedLong(update.expiresInMs) / 1000); + final int duration = Math.toIntExact(update.expiresInMs / 1000); String time = ExpirationUtil.getExpirationDisplayValue(context, duration); if (update.updaterAci == null) { updates.add(updateDescription(context.getString(R.string.MessageRecord_disappearing_message_time_set_to_s, time), R.drawable.ic_update_timer_16)); diff --git a/app/src/main/protowire/Backup.proto b/app/src/main/protowire/Backup.proto index 691316c60c..aaf9e9ebc3 100644 --- a/app/src/main/protowire/Backup.proto +++ b/app/src/main/protowire/Backup.proto @@ -63,7 +63,7 @@ message AccountData { bool linkPreviews = 4; bool notDiscoverableByPhoneNumber = 5; bool preferContactAvatars = 6; - uint32 universalExpireTimer = 7; // 0 means no universal expire timer. + uint32 universalExpireTimerSeconds = 7; // 0 means no universal expire timer. repeated string preferredReactionEmoji = 8; bool displayBadgesOnProfile = 9; bool keepMutedChatsArchived = 10; @@ -75,6 +75,7 @@ message AccountData { bool hasCompletedUsernameOnboarding = 16; PhoneNumberSharingMode phoneNumberSharingMode = 17; ChatStyle defaultChatStyle = 18; + repeated ChatStyle.CustomChatColor customChatColors = 19; } message SubscriberData { @@ -564,7 +565,7 @@ message FilePointer { optional uint32 cdnNumber = 2; bytes key = 3; bytes digest = 4; - uint32 size = 5; + uint64 size = 5; // Fallback in case backup tier upload failed. optional string transitCdnKey = 6; optional uint32 transitCdnNumber = 7; @@ -756,7 +757,7 @@ message SimpleChatUpdate { // For 1:1 chat updates only. // For group thread updates use GroupExpirationTimerUpdate. message ExpirationTimerChatUpdate { - uint32 expiresInMs = 1; // 0 means the expiration timer was disabled + uint64 expiresInMs = 1; // 0 means the expiration timer was disabled } message ProfileChangeChatUpdate { @@ -1022,7 +1023,7 @@ message GroupV2MigrationDroppedMembersUpdate { // For 1:1 timer updates, use ExpirationTimerChatUpdate. message GroupExpirationTimerUpdate { - uint32 expiresInMs = 1; // 0 means the expiration timer was disabled + uint64 expiresInMs = 1; // 0 means the expiration timer was disabled optional bytes updaterAci = 2; } @@ -1034,10 +1035,19 @@ message StickerPack { message ChatStyle { message Gradient { uint32 angle = 1; // degrees - repeated uint32 colors = 2; + repeated fixed32 colors = 2; repeated float positions = 3; // percent from 0 to 1 } + message CustomChatColor { + uint64 id = 1; + + oneof color { + fixed32 solid = 2; + Gradient gradient = 3; + } + } + message AutomaticBubbleColor { } @@ -1098,10 +1108,14 @@ message ChatStyle { } oneof bubbleColor { - BubbleColorPreset bubbleColorPreset = 3; - Gradient bubbleGradient = 4; - uint32 bubbleSolidColor = 5; - // Bubble setting is automatically determined based on the wallpaper setting. - AutomaticBubbleColor autoBubbleColor = 6; + // Bubble setting is automatically determined based on the wallpaper setting, + // or `SOLID_ULTRAMARINE` for `noWallpaper` + AutomaticBubbleColor autoBubbleColor = 3; + BubbleColorPreset bubbleColorPreset = 4; + + // See AccountSettings.customChatColors + uint64 customColorId = 5; } + + bool dimWallpaperInDarkMode = 7; } \ No newline at end of file