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 da1b53ad97..3283d3242c 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 @@ -11,6 +11,7 @@ import org.json.JSONArray import org.json.JSONException import org.signal.core.util.Base64 import org.signal.core.util.Hex +import org.signal.core.util.emptyIfNull import org.signal.core.util.logging.Log import org.signal.core.util.orNull import org.signal.core.util.requireBlob @@ -636,8 +637,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize: familyName = familyName, prefix = prefix, suffix = suffix, - middleName = middleName, - displayName = displayName + middleName = middleName ) } @@ -699,9 +699,11 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize: Quote( targetSentTimestamp = this.quoteTargetSentTimestamp.takeIf { !this.quoteMissing && it != MessageTable.QUOTE_TARGET_MISSING_ID }, authorId = this.quoteAuthor, - text = this.quoteBody, + text = Text( + body = this.quoteBody.emptyIfNull(), + bodyRanges = this.quoteBodyRanges?.toBackupBodyRanges() ?: emptyList() + ), attachments = attachments?.toBackupQuoteAttachments() ?: emptyList(), - bodyRanges = this.quoteBodyRanges?.toBackupBodyRanges() ?: emptyList(), type = when (type) { QuoteModel.Type.NORMAL -> Quote.Type.NORMAL QuoteModel.Type.GIFT_BADGE -> Quote.Type.GIFTBADGE @@ -906,8 +908,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize: emoji = it.emoji, authorId = it.author.toLong(), sentTimestamp = it.dateSent, - receivedTimestamp = it.dateReceived, - sortOrder = 0 // TODO [backup] make this it.dateReceived once comparator support is added + sortOrder = it.dateReceived ) } ?: emptyList() } @@ -928,12 +929,12 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize: when { this.identityMismatchRecipientIds.contains(this.toRecipientId) -> { statusBuilder.failed = SendStatus.Failed( - identityKeyMismatch = true + reason = SendStatus.Failed.FailureReason.IDENTITY_KEY_MISMATCH ) } this.networkFailureRecipientIds.contains(this.toRecipientId) -> { statusBuilder.failed = SendStatus.Failed( - network = true + reason = SendStatus.Failed.FailureReason.NETWORK ) } this.baseType == MessageTypes.BASE_SENT_TYPE -> { @@ -973,12 +974,12 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize: when { identityMismatchRecipientIds.contains(it.recipientId.toLong()) -> { statusBuilder.failed = SendStatus.Failed( - identityKeyMismatch = true + reason = SendStatus.Failed.FailureReason.IDENTITY_KEY_MISMATCH ) } networkFailureRecipientIds.contains(it.recipientId.toLong()) -> { statusBuilder.failed = SendStatus.Failed( - network = true + reason = SendStatus.Failed.FailureReason.NETWORK ) } it.status == GroupReceiptTable.STATUS_UNKNOWN -> { 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 e7b71a3801..ab71d2dc1e 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 @@ -69,6 +69,7 @@ import org.thoughtcrime.securesms.payments.CryptoValueUtil import org.thoughtcrime.securesms.payments.Direction import org.thoughtcrime.securesms.payments.State import org.thoughtcrime.securesms.payments.proto.PaymentMetaData +import org.thoughtcrime.securesms.profiles.ProfileName import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.stickers.StickerLocator @@ -531,7 +532,7 @@ class ChatItemImportInserter( ReactionTable.MESSAGE_ID to messageId, ReactionTable.AUTHOR_ID to authorId, ReactionTable.DATE_SENT to it.sentTimestamp, - ReactionTable.DATE_RECEIVED to (it.receivedTimestamp ?: it.sortOrder), + ReactionTable.DATE_RECEIVED to it.sortOrder, ReactionTable.EMOJI to it.emoji ) } else { @@ -571,9 +572,9 @@ class ChatItemImportInserter( private fun ChatItem.getMessageType(): Long { var type: Long = if (this.outgoing != null) { - if (this.outgoing.sendStatus.count { it.failed?.identityKeyMismatch == true } > 0) { + if (this.outgoing.sendStatus.count { it.failed?.reason == SendStatus.Failed.FailureReason.IDENTITY_KEY_MISMATCH } > 0) { MessageTypes.BASE_SENT_FAILED_TYPE - } else if (this.outgoing.sendStatus.count { it.failed?.network == true } > 0) { + } else if (this.outgoing.sendStatus.count { it.failed?.reason == SendStatus.Failed.FailureReason.NETWORK } > 0) { MessageTypes.BASE_SENDING_TYPE } else { MessageTypes.BASE_SENT_TYPE @@ -812,10 +813,10 @@ 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, importState.remoteToLocalRecipientId[quote.authorId]!!.serialize()) - this.put(MessageTable.QUOTE_BODY, quote.text) + this.put(MessageTable.QUOTE_BODY, quote.text?.body) this.put(MessageTable.QUOTE_TYPE, quote.type.toLocalQuoteType()) - this.put(MessageTable.QUOTE_BODY_RANGES, quote.bodyRanges.toLocalBodyRanges()?.encode()) - // TODO quote attachments + this.put(MessageTable.QUOTE_BODY_RANGES, quote.text?.bodyRanges?.toLocalBodyRanges()?.encode()) + // TODO [backup] quote attachments this.put(MessageTable.QUOTE_MISSING, (quote.targetSentTimestamp == null).toInt()) } @@ -842,7 +843,7 @@ class ChatItemImportInserter( } val networkFailures = chatItem.outgoing.sendStatus - .filter { status -> status.failed?.network ?: false } + .filter { status -> status.failed?.reason == SendStatus.Failed.FailureReason.NETWORK } .mapNotNull { status -> importState.remoteToLocalRecipientId[status.recipientId] } .map { recipientId -> NetworkFailure(recipientId) } .toSet() @@ -858,7 +859,7 @@ class ChatItemImportInserter( } val mismatches = chatItem.outgoing.sendStatus - .filter { status -> status.failed?.identityKeyMismatch ?: false } + .filter { status -> status.failed?.reason == SendStatus.Failed.FailureReason.IDENTITY_KEY_MISMATCH } .mapNotNull { status -> importState.remoteToLocalRecipientId[status.recipientId] } .map { recipientId -> IdentityKeyMismatch(recipientId, null) } // TODO We probably want the actual identity key in this status situation? .toSet() @@ -1057,7 +1058,8 @@ class ChatItemImportInserter( } private fun ContactAttachment.Name?.toLocal(): Contact.Name { - return Contact.Name(this?.displayName, this?.givenName, this?.familyName, this?.prefix, this?.suffix, this?.middleName) + val displayName = ProfileName.fromParts(this?.givenName, this?.familyName).toString() + return Contact.Name(displayName, this?.givenName, this?.familyName, this?.prefix, this?.suffix, this?.middleName) } private fun ContactAttachment.Phone.Type?.toLocal(): Contact.Phone.Type { diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/RecipientTableBackupExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/RecipientTableBackupExtensions.kt index 643e67d783..7d7fe0a21d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/RecipientTableBackupExtensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/RecipientTableBackupExtensions.kt @@ -324,11 +324,11 @@ private fun DecryptedGroup.toSnapshot(): Group.GroupSnapshot? { } private fun Group.Member.toLocal(): DecryptedMember { - return DecryptedMember(aciBytes = userId, role = role.toLocal(), profileKey = profileKey, joinedAtRevision = joinedAtVersion) + return DecryptedMember(aciBytes = userId, role = role.toLocal(), joinedAtRevision = joinedAtVersion) } private fun DecryptedMember.toSnapshot(): Group.Member { - return Group.Member(userId = aciBytes, role = role.toSnapshot(), profileKey = profileKey, joinedAtVersion = joinedAtRevision) + return Group.Member(userId = aciBytes, role = role.toSnapshot(), joinedAtVersion = joinedAtRevision) } private fun Group.MemberPendingProfileKey.toLocal(operations: GroupsV2Operations.GroupOperations): DecryptedPendingMember { @@ -355,7 +355,6 @@ private fun DecryptedPendingMember.toSnapshot(): Group.MemberPendingProfileKey { private fun Group.MemberPendingAdminApproval.toLocal(): DecryptedRequestingMember { return DecryptedRequestingMember( aciBytes = this.userId, - profileKey = this.profileKey, timestamp = this.timestamp ) } @@ -363,7 +362,6 @@ private fun Group.MemberPendingAdminApproval.toLocal(): DecryptedRequestingMembe private fun DecryptedRequestingMember.toSnapshot(): Group.MemberPendingAdminApproval { return Group.MemberPendingAdminApproval( userId = this.aciBytes, - profileKey = this.profileKey, timestamp = this.timestamp ) } 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 792c8127a1..c2f4036617 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 @@ -39,6 +39,7 @@ fun ThreadTable.getThreadsForBackup(): ChatExportIterator { ${ThreadTable.READ}, ${ThreadTable.ARCHIVED}, ${RecipientTable.TABLE_NAME}.${RecipientTable.MESSAGE_EXPIRATION_TIME}, + ${RecipientTable.TABLE_NAME}.${RecipientTable.MESSAGE_EXPIRATION_TIME_VERSION}, ${RecipientTable.TABLE_NAME}.${RecipientTable.MUTE_UNTIL}, ${RecipientTable.TABLE_NAME}.${RecipientTable.MENTION_SETTING}, ${RecipientTable.TABLE_NAME}.${RecipientTable.CHAT_COLORS}, @@ -73,6 +74,7 @@ fun ThreadTable.restoreFromBackup(chat: Chat, recipientId: RecipientId, importSt ThreadTable.ACTIVE to 1 ) .run() + writableDatabase .update( RecipientTable.TABLE_NAME, @@ -80,6 +82,7 @@ fun ThreadTable.restoreFromBackup(chat: Chat, recipientId: RecipientId, importSt 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.MESSAGE_EXPIRATION_TIME_VERSION to chat.expireTimerVersion, RecipientTable.CHAT_COLORS to chatColor?.serialize()?.encode(), RecipientTable.CUSTOM_CHAT_COLORS_ID to (chatColor?.id ?: ChatColors.Id.NotSet).longValue ), @@ -116,6 +119,7 @@ class ChatExportIterator(private val cursor: Cursor) : Iterator, Closeable archived = cursor.requireBoolean(ThreadTable.ARCHIVED), pinnedOrder = cursor.requireInt(ThreadTable.PINNED), expirationTimerMs = cursor.requireLong(RecipientTable.MESSAGE_EXPIRATION_TIME), + expireTimerVersion = cursor.requireInt(RecipientTable.MESSAGE_EXPIRATION_TIME_VERSION), muteUntilMs = cursor.requireLong(RecipientTable.MUTE_UNTIL), markedUnread = ThreadTable.ReadStatus.deserialize(cursor.requireInt(ThreadTable.READ)) == ThreadTable.ReadStatus.FORCED_UNREAD, dontNotifyForMentionsIfMuted = RecipientTable.MentionSetting.DO_NOT_NOTIFY.id == cursor.requireInt(RecipientTable.MENTION_SETTING), diff --git a/app/src/main/java/org/thoughtcrime/securesms/contactshare/Contact.java b/app/src/main/java/org/thoughtcrime/securesms/contactshare/Contact.java index 36f50ab5fd..487fab8105 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contactshare/Contact.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contactshare/Contact.java @@ -160,7 +160,8 @@ public class Contact implements Parcelable { @JsonProperty private final String middleName; - public Name(@JsonProperty("displayName") @Nullable String displayName, + public Name( + @JsonProperty("displayName") @Nullable String displayName, @JsonProperty("givenName") @Nullable String givenName, @JsonProperty("familyName") @Nullable String familyName, @JsonProperty("prefix") @Nullable String prefix, diff --git a/app/src/main/protowire/Backup.proto b/app/src/main/protowire/Backup.proto index cb3908bae2..b0b63315c8 100644 --- a/app/src/main/protowire/Backup.proto +++ b/app/src/main/protowire/Backup.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package signal.backup; option java_package = "org.thoughtcrime.securesms.backup.v2.proto"; +option swift_prefix = "BackupProto_"; message BackupInfo { uint64 version = 1; @@ -190,8 +191,8 @@ message Group { bytes userId = 1; Role role = 2; - bytes profileKey = 3; - reserved /*presentation*/ 4; // The field is deprecated in the context of static group state + reserved /*profileKey*/ 3; // This field is ignored in Backups, in favor of Contact frames for members + reserved /*presentation*/ 4; // This field is deprecated in the context of static group state uint32 joinedAtVersion = 5; } @@ -203,8 +204,8 @@ message Group { message MemberPendingAdminApproval { bytes userId = 1; - bytes profileKey = 2; - reserved /*presentation*/ 3; // The field is deprecated in the context of static group state + reserved /*profileKey*/ 2; // This field is ignored in Backups, in favor of Contact frames for members + reserved /*presentation*/ 3; // This field is deprecated in the context of static group state uint64 timestamp = 4; } @@ -242,6 +243,7 @@ message Chat { bool markedUnread = 7; bool dontNotifyForMentionsIfMuted = 8; ChatStyle style = 9; + uint32 expireTimerVersion = 10; } /** @@ -365,10 +367,13 @@ message SendStatus { message Skipped {} message Failed { - oneof reason { - bool network = 1; - bool identityKeyMismatch = 2; + enum FailureReason { + UNKNOWN = 0; // A valid value -- could indicate a crash or lack of information + NETWORK = 1; + IDENTITY_KEY_MISMATCH = 2; } + + FailureReason reason = 1; } uint64 recipientId = 1; @@ -471,7 +476,6 @@ message ContactAttachment { optional string prefix = 3; optional string suffix = 4; optional string middleName = 5; - optional string displayName = 6; } message Phone { @@ -646,10 +650,9 @@ message Quote { optional uint64 targetSentTimestamp = 1; // null if the target message could not be found at time of quote insert uint64 authorId = 2; - optional string text = 3; + optional Text text = 3; repeated QuotedAttachment attachments = 4; - repeated BodyRange bodyRanges = 5; - Type type = 6; + Type type = 5; } message BodyRange { @@ -675,11 +678,9 @@ message Reaction { string emoji = 1; uint64 authorId = 2; uint64 sentTimestamp = 3; - // Optional because some clients may not track this data - optional uint64 receivedTimestamp = 4; // A higher sort order means that a reaction is more recent. Some clients may export this as // incrementing numbers (e.g. 1, 2, 3), others as timestamps. - uint64 sortOrder = 5; + uint64 sortOrder = 4; } message ChatUpdateMessage { @@ -726,6 +727,7 @@ message IndividualCall { Direction direction = 3; State state = 4; uint64 startedCallTimestamp = 5; + bool read = 6; } message GroupCall { @@ -757,6 +759,7 @@ message GroupCall { uint64 startedCallTimestamp = 5; // The time the call ended. 0 indicates an unknown time. uint64 endedCallTimestamp = 6; + bool read = 7; } message SimpleChatUpdate { @@ -777,7 +780,7 @@ message SimpleChatUpdate { REPORTED_SPAM = 13; BLOCKED = 14; UNBLOCKED = 15; - ACCEPTED = 16; + MESSAGE_REQUEST_ACCEPTED = 16; } Type type = 1; @@ -1064,7 +1067,7 @@ message StickerPack { message ChatStyle { message Gradient { uint32 angle = 1; // degrees - repeated fixed32 colors = 2; + repeated fixed32 colors = 2; // 0xAARRGGBB repeated float positions = 3; // percent from 0 to 1 } @@ -1072,7 +1075,7 @@ message ChatStyle { uint64 id = 1; oneof color { - fixed32 solid = 2; + fixed32 solid = 2; // 0xAARRGGBB Gradient gradient = 3; } } @@ -1133,6 +1136,8 @@ message ChatStyle { oneof wallpaper { WallpaperPreset wallpaperPreset = 1; + // This `FilePointer` is expected not to contain a `fileName`, `width`, + // `height`, or `caption`. FilePointer wallpaperPhoto = 2; }