mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-24 03:35:58 +00:00
Add support for new backup calls proto and call links.
This commit is contained in:
@@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.backup.v2.database.ChatItemImportInserter
|
||||
import org.thoughtcrime.securesms.backup.v2.database.clearAllDataForBackupRestore
|
||||
import org.thoughtcrime.securesms.backup.v2.processor.AccountDataProcessor
|
||||
import org.thoughtcrime.securesms.backup.v2.processor.CallLogBackupProcessor
|
||||
import org.thoughtcrime.securesms.backup.v2.processor.AdHocCallBackupProcessor
|
||||
import org.thoughtcrime.securesms.backup.v2.processor.ChatBackupProcessor
|
||||
import org.thoughtcrime.securesms.backup.v2.processor.ChatItemBackupProcessor
|
||||
import org.thoughtcrime.securesms.backup.v2.processor.RecipientBackupProcessor
|
||||
@@ -109,7 +109,7 @@ object BackupRepository {
|
||||
eventTimer.emit("thread")
|
||||
}
|
||||
|
||||
CallLogBackupProcessor.export { frame ->
|
||||
AdHocCallBackupProcessor.export { frame ->
|
||||
writer.write(frame)
|
||||
eventTimer.emit("call")
|
||||
}
|
||||
@@ -198,8 +198,8 @@ object BackupRepository {
|
||||
eventTimer.emit("chat")
|
||||
}
|
||||
|
||||
frame.call != null -> {
|
||||
CallLogBackupProcessor.import(frame.call, backupState)
|
||||
frame.adHocCall != null -> {
|
||||
AdHocCallBackupProcessor.import(frame.adHocCall, backupState)
|
||||
eventTimer.emit("call")
|
||||
}
|
||||
|
||||
@@ -644,7 +644,6 @@ class BackupState(val backupKey: BackupKey) {
|
||||
val chatIdToLocalThreadId = HashMap<Long, Long>()
|
||||
val chatIdToLocalRecipientId = HashMap<Long, RecipientId>()
|
||||
val chatIdToBackupRecipientId = HashMap<Long, Long>()
|
||||
val callIdToType = HashMap<Long, Long>()
|
||||
}
|
||||
|
||||
class BackupMetadata(
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2.database
|
||||
|
||||
import android.database.Cursor
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.signal.core.util.select
|
||||
import org.signal.ringrtc.CallLinkRootKey
|
||||
import org.signal.ringrtc.CallLinkState
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.CallLink
|
||||
import org.thoughtcrime.securesms.database.CallLinkTable
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkState
|
||||
import java.io.Closeable
|
||||
import java.time.Instant
|
||||
|
||||
fun CallLinkTable.getCallLinksForBackup(): BackupCallLinkIterator {
|
||||
val cursor = readableDatabase
|
||||
.select()
|
||||
.from(CallLinkTable.TABLE_NAME)
|
||||
.run()
|
||||
|
||||
return BackupCallLinkIterator(cursor)
|
||||
}
|
||||
|
||||
fun CallLinkTable.restoreFromBackup(callLink: CallLink): RecipientId {
|
||||
return SignalDatabase.callLinks.insertCallLink(
|
||||
CallLinkTable.CallLink(
|
||||
recipientId = RecipientId.UNKNOWN,
|
||||
roomId = CallLinkRoomId.fromCallLinkRootKey(CallLinkRootKey(callLink.rootKey.toByteArray())),
|
||||
credentials = CallLinkCredentials(callLink.rootKey.toByteArray(), callLink.adminKey?.toByteArray()),
|
||||
state = SignalCallLinkState(
|
||||
name = callLink.name,
|
||||
restrictions = callLink.restrictions.toLocal(),
|
||||
expiration = Instant.ofEpochMilli(callLink.expirationMs)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a nice iterable interface over a [RecipientTable] cursor, converting rows to [BackupRecipient]s.
|
||||
* Important: Because this is backed by a cursor, you must close it. It's recommended to use `.use()` or try-with-resources.
|
||||
*/
|
||||
class BackupCallLinkIterator(private val cursor: Cursor) : Iterator<BackupRecipient>, Closeable {
|
||||
override fun hasNext(): Boolean {
|
||||
return cursor.count > 0 && !cursor.isLast
|
||||
}
|
||||
|
||||
override fun next(): BackupRecipient {
|
||||
if (!cursor.moveToNext()) {
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
|
||||
val callLink = CallLinkTable.CallLinkDeserializer.deserialize(cursor)
|
||||
return BackupRecipient(
|
||||
id = callLink.recipientId.toLong(),
|
||||
callLink = CallLink(
|
||||
rootKey = callLink.credentials!!.linkKeyBytes.toByteString(),
|
||||
adminKey = callLink.credentials.adminPassBytes?.toByteString(),
|
||||
name = callLink.state.name,
|
||||
expirationMs = callLink.state.expiration.toEpochMilli(),
|
||||
restrictions = callLink.state.restrictions.toBackup()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun CallLinkState.Restrictions.toBackup(): CallLink.Restrictions {
|
||||
return when (this) {
|
||||
CallLinkState.Restrictions.ADMIN_APPROVAL -> CallLink.Restrictions.ADMIN_APPROVAL
|
||||
CallLinkState.Restrictions.NONE -> CallLink.Restrictions.NONE
|
||||
CallLinkState.Restrictions.UNKNOWN -> CallLink.Restrictions.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
private fun CallLink.Restrictions.toLocal(): CallLinkState.Restrictions {
|
||||
return when (this) {
|
||||
CallLink.Restrictions.ADMIN_APPROVAL -> CallLinkState.Restrictions.ADMIN_APPROVAL
|
||||
CallLink.Restrictions.NONE -> CallLinkState.Restrictions.NONE
|
||||
CallLink.Restrictions.UNKNOWN -> CallLinkState.Restrictions.UNKNOWN
|
||||
}
|
||||
}
|
||||
@@ -8,57 +8,37 @@ package org.thoughtcrime.securesms.backup.v2.database
|
||||
import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import androidx.core.content.contentValuesOf
|
||||
import org.signal.core.util.isNull
|
||||
import org.signal.core.util.requireInt
|
||||
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.proto.Call
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.AdHocCall
|
||||
import org.thoughtcrime.securesms.database.CallTable
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import java.io.Closeable
|
||||
|
||||
typealias BackupCall = org.thoughtcrime.securesms.backup.v2.proto.Call
|
||||
|
||||
fun CallTable.getCallsForBackup(): CallLogIterator {
|
||||
fun CallTable.getAdhocCallsForBackup(): CallLogIterator {
|
||||
return CallLogIterator(
|
||||
readableDatabase
|
||||
.select()
|
||||
.from(CallTable.TABLE_NAME)
|
||||
.where("${CallTable.EVENT} != ${CallTable.Event.serialize(CallTable.Event.DELETE)}")
|
||||
.where("${CallTable.TYPE}=?", CallTable.Type.AD_HOC_CALL)
|
||||
.run()
|
||||
)
|
||||
}
|
||||
|
||||
fun CallTable.restoreCallLogFromBackup(call: BackupCall, backupState: BackupState) {
|
||||
val type = when (call.type) {
|
||||
Call.Type.VIDEO_CALL -> CallTable.Type.VIDEO_CALL
|
||||
Call.Type.AUDIO_CALL -> CallTable.Type.AUDIO_CALL
|
||||
Call.Type.AD_HOC_CALL -> CallTable.Type.AD_HOC_CALL
|
||||
Call.Type.GROUP_CALL -> CallTable.Type.GROUP_CALL
|
||||
Call.Type.UNKNOWN_TYPE -> return
|
||||
}
|
||||
|
||||
fun CallTable.restoreCallLogFromBackup(call: AdHocCall, backupState: BackupState) {
|
||||
val event = when (call.state) {
|
||||
Call.State.MISSED -> CallTable.Event.MISSED
|
||||
Call.State.COMPLETED -> CallTable.Event.ACCEPTED
|
||||
Call.State.DECLINED_BY_USER -> CallTable.Event.DECLINED
|
||||
Call.State.DECLINED_BY_NOTIFICATION_PROFILE -> CallTable.Event.MISSED_NOTIFICATION_PROFILE
|
||||
Call.State.UNKNOWN_EVENT -> return
|
||||
AdHocCall.State.GENERIC -> CallTable.Event.GENERIC_GROUP_CALL
|
||||
AdHocCall.State.UNKNOWN_STATE -> CallTable.Event.GENERIC_GROUP_CALL
|
||||
}
|
||||
|
||||
val direction = if (call.outgoing) CallTable.Direction.OUTGOING else CallTable.Direction.INCOMING
|
||||
|
||||
backupState.callIdToType[call.callId] = CallTable.Call.getMessageType(type, direction, event)
|
||||
|
||||
val values = contentValuesOf(
|
||||
CallTable.CALL_ID to call.callId,
|
||||
CallTable.PEER to backupState.backupToLocalRecipientId[call.conversationRecipientId]!!.serialize(),
|
||||
CallTable.TYPE to CallTable.Type.serialize(type),
|
||||
CallTable.DIRECTION to CallTable.Direction.serialize(direction),
|
||||
CallTable.PEER to backupState.backupToLocalRecipientId[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),
|
||||
CallTable.TIMESTAMP to call.timestamp,
|
||||
CallTable.RINGER to if (call.ringerRecipientId != null) backupState.backupToLocalRecipientId[call.ringerRecipientId]?.toLong() else null
|
||||
CallTable.TIMESTAMP to call.startedCallTimestamp
|
||||
)
|
||||
|
||||
writableDatabase.insert(CallTable.TABLE_NAME, SQLiteDatabase.CONFLICT_IGNORE, values)
|
||||
@@ -68,49 +48,23 @@ fun CallTable.restoreCallLogFromBackup(call: BackupCall, backupState: BackupStat
|
||||
* Provides a nice iterable interface over a [RecipientTable] cursor, converting rows to [BackupRecipient]s.
|
||||
* Important: Because this is backed by a cursor, you must close it. It's recommended to use `.use()` or try-with-resources.
|
||||
*/
|
||||
class CallLogIterator(private val cursor: Cursor) : Iterator<BackupCall?>, Closeable {
|
||||
class CallLogIterator(private val cursor: Cursor) : Iterator<AdHocCall?>, Closeable {
|
||||
override fun hasNext(): Boolean {
|
||||
return cursor.count > 0 && !cursor.isLast
|
||||
}
|
||||
|
||||
override fun next(): BackupCall? {
|
||||
override fun next(): AdHocCall? {
|
||||
if (!cursor.moveToNext()) {
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
|
||||
val callId = cursor.requireLong(CallTable.CALL_ID)
|
||||
val type = CallTable.Type.deserialize(cursor.requireInt(CallTable.TYPE))
|
||||
val direction = CallTable.Direction.deserialize(cursor.requireInt(CallTable.DIRECTION))
|
||||
val event = CallTable.Event.deserialize(cursor.requireInt(CallTable.EVENT))
|
||||
|
||||
return BackupCall(
|
||||
return AdHocCall(
|
||||
callId = callId,
|
||||
conversationRecipientId = cursor.requireLong(CallTable.PEER),
|
||||
type = when (type) {
|
||||
CallTable.Type.AUDIO_CALL -> Call.Type.AUDIO_CALL
|
||||
CallTable.Type.VIDEO_CALL -> Call.Type.VIDEO_CALL
|
||||
CallTable.Type.AD_HOC_CALL -> Call.Type.AD_HOC_CALL
|
||||
CallTable.Type.GROUP_CALL -> Call.Type.GROUP_CALL
|
||||
},
|
||||
outgoing = when (direction) {
|
||||
CallTable.Direction.OUTGOING -> true
|
||||
else -> false
|
||||
},
|
||||
timestamp = cursor.requireLong(CallTable.TIMESTAMP),
|
||||
ringerRecipientId = if (cursor.isNull(CallTable.RINGER)) null else cursor.requireLong(CallTable.RINGER),
|
||||
state = when (event) {
|
||||
CallTable.Event.ONGOING -> Call.State.COMPLETED
|
||||
CallTable.Event.OUTGOING_RING -> Call.State.COMPLETED
|
||||
CallTable.Event.ACCEPTED -> Call.State.COMPLETED
|
||||
CallTable.Event.DECLINED -> Call.State.DECLINED_BY_USER
|
||||
CallTable.Event.GENERIC_GROUP_CALL -> Call.State.COMPLETED
|
||||
CallTable.Event.JOINED -> Call.State.COMPLETED
|
||||
CallTable.Event.MISSED -> Call.State.MISSED
|
||||
CallTable.Event.MISSED_NOTIFICATION_PROFILE -> Call.State.DECLINED_BY_NOTIFICATION_PROFILE
|
||||
CallTable.Event.DELETE -> Call.State.COMPLETED
|
||||
CallTable.Event.RINGING -> Call.State.MISSED
|
||||
CallTable.Event.NOT_ACCEPTED -> Call.State.MISSED
|
||||
}
|
||||
recipientId = cursor.requireLong(CallTable.PEER),
|
||||
state = AdHocCall.State.GENERIC,
|
||||
startedCallTimestamp = cursor.requireLong(CallTable.TIMESTAMP)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
package org.thoughtcrime.securesms.backup.v2.database
|
||||
|
||||
import android.database.Cursor
|
||||
import com.annimon.stream.Stream
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.Base64.decode
|
||||
@@ -20,13 +19,12 @@ import org.signal.core.util.requireString
|
||||
import org.thoughtcrime.securesms.attachments.Cdn
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository.getMediaName
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.CallChatUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatUpdateMessage
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ExpirationTimerChatUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.FilePointer
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupCallChatUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.IndividualCallChatUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupCall
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.IndividualCall
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.MessageAttachment
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ProfileChangeChatUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Quote
|
||||
@@ -39,6 +37,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.StandardMessage
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Text
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ThreadMergeChatUpdate
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.database.CallTable
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptTable
|
||||
import org.thoughtcrime.securesms.database.MessageTable
|
||||
import org.thoughtcrime.securesms.database.MessageTypes
|
||||
@@ -66,7 +65,6 @@ import java.io.Closeable
|
||||
import java.io.IOException
|
||||
import java.util.LinkedList
|
||||
import java.util.Queue
|
||||
import java.util.UUID
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.BodyRange as BackupBodyRange
|
||||
|
||||
/**
|
||||
@@ -139,7 +137,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
||||
MessageTypes.isPaymentsRequestToActivate(record.type) -> builder.updateMessage = ChatUpdateMessage(simpleUpdate = SimpleChatUpdate(type = SimpleChatUpdate.Type.PAYMENT_ACTIVATION_REQUEST))
|
||||
MessageTypes.isExpirationTimerUpdate(record.type) -> {
|
||||
builder.updateMessage = ChatUpdateMessage(expirationTimerChange = ExpirationTimerChatUpdate(record.expiresIn.toInt()))
|
||||
builder.expiresInMs = null
|
||||
builder.expiresInMs = 0
|
||||
}
|
||||
MessageTypes.isProfileChange(record.type) -> {
|
||||
if (record.body == null) continue
|
||||
@@ -203,54 +201,112 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
||||
builder.sms = false
|
||||
val call = calls.getCallByMessageId(record.id)
|
||||
if (call != null) {
|
||||
builder.updateMessage = ChatUpdateMessage(callingMessage = CallChatUpdate(callId = call.callId))
|
||||
if (call.type == CallTable.Type.GROUP_CALL) {
|
||||
builder.updateMessage = ChatUpdateMessage(
|
||||
groupCall = GroupCall(
|
||||
callId = record.id,
|
||||
state = when (call.event) {
|
||||
CallTable.Event.MISSED -> GroupCall.State.MISSED
|
||||
CallTable.Event.ONGOING -> GroupCall.State.GENERIC
|
||||
CallTable.Event.ACCEPTED -> GroupCall.State.ACCEPTED
|
||||
CallTable.Event.NOT_ACCEPTED -> GroupCall.State.GENERIC
|
||||
CallTable.Event.MISSED_NOTIFICATION_PROFILE -> GroupCall.State.MISSED_NOTIFICATION_PROFILE
|
||||
CallTable.Event.DELETE -> continue
|
||||
CallTable.Event.GENERIC_GROUP_CALL -> GroupCall.State.GENERIC
|
||||
CallTable.Event.JOINED -> GroupCall.State.JOINED
|
||||
CallTable.Event.RINGING -> GroupCall.State.RINGING
|
||||
CallTable.Event.DECLINED -> GroupCall.State.DECLINED
|
||||
CallTable.Event.OUTGOING_RING -> GroupCall.State.OUTGOING_RING
|
||||
},
|
||||
ringerRecipientId = call.ringerRecipient?.toLong(),
|
||||
startedCallAci = if (call.ringerRecipient != null) SignalDatabase.recipients.getRecord(call.ringerRecipient).aci?.toByteString() else null,
|
||||
startedCallTimestamp = call.timestamp
|
||||
)
|
||||
)
|
||||
} else if (call.type != CallTable.Type.AD_HOC_CALL) {
|
||||
builder.updateMessage = ChatUpdateMessage(
|
||||
individualCall = IndividualCall(
|
||||
callId = call.callId,
|
||||
type = if (call.type == CallTable.Type.VIDEO_CALL) IndividualCall.Type.VIDEO_CALL else IndividualCall.Type.AUDIO_CALL,
|
||||
direction = if (call.direction == CallTable.Direction.INCOMING) IndividualCall.Direction.INCOMING else IndividualCall.Direction.OUTGOING,
|
||||
state = when (call.event) {
|
||||
CallTable.Event.MISSED -> IndividualCall.State.MISSED
|
||||
CallTable.Event.MISSED_NOTIFICATION_PROFILE -> IndividualCall.State.MISSED_NOTIFICATION_PROFILE
|
||||
CallTable.Event.ACCEPTED -> IndividualCall.State.ACCEPTED
|
||||
CallTable.Event.NOT_ACCEPTED -> IndividualCall.State.NOT_ACCEPTED
|
||||
else -> IndividualCall.State.UNKNOWN_STATE
|
||||
},
|
||||
startedCallTimestamp = call.timestamp
|
||||
)
|
||||
)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
when {
|
||||
MessageTypes.isMissedAudioCall(record.type) -> {
|
||||
builder.updateMessage = ChatUpdateMessage(callingMessage = CallChatUpdate(callMessage = IndividualCallChatUpdate(type = IndividualCallChatUpdate.Type.MISSED_INCOMING_AUDIO_CALL)))
|
||||
builder.updateMessage = ChatUpdateMessage(
|
||||
individualCall = IndividualCall(
|
||||
type = IndividualCall.Type.AUDIO_CALL,
|
||||
state = IndividualCall.State.MISSED,
|
||||
direction = IndividualCall.Direction.INCOMING
|
||||
)
|
||||
)
|
||||
}
|
||||
MessageTypes.isMissedVideoCall(record.type) -> {
|
||||
builder.updateMessage = ChatUpdateMessage(callingMessage = CallChatUpdate(callMessage = IndividualCallChatUpdate(type = IndividualCallChatUpdate.Type.MISSED_INCOMING_VIDEO_CALL)))
|
||||
builder.updateMessage = ChatUpdateMessage(
|
||||
individualCall = IndividualCall(
|
||||
type = IndividualCall.Type.VIDEO_CALL,
|
||||
state = IndividualCall.State.MISSED,
|
||||
direction = IndividualCall.Direction.INCOMING
|
||||
)
|
||||
)
|
||||
}
|
||||
MessageTypes.isIncomingAudioCall(record.type) -> {
|
||||
builder.updateMessage = ChatUpdateMessage(callingMessage = CallChatUpdate(callMessage = IndividualCallChatUpdate(type = IndividualCallChatUpdate.Type.INCOMING_AUDIO_CALL)))
|
||||
builder.updateMessage = ChatUpdateMessage(
|
||||
individualCall = IndividualCall(
|
||||
type = IndividualCall.Type.AUDIO_CALL,
|
||||
state = IndividualCall.State.ACCEPTED,
|
||||
direction = IndividualCall.Direction.INCOMING
|
||||
)
|
||||
)
|
||||
}
|
||||
MessageTypes.isIncomingVideoCall(record.type) -> {
|
||||
builder.updateMessage = ChatUpdateMessage(callingMessage = CallChatUpdate(callMessage = IndividualCallChatUpdate(type = IndividualCallChatUpdate.Type.INCOMING_VIDEO_CALL)))
|
||||
builder.updateMessage = ChatUpdateMessage(
|
||||
individualCall = IndividualCall(
|
||||
type = IndividualCall.Type.VIDEO_CALL,
|
||||
state = IndividualCall.State.ACCEPTED,
|
||||
direction = IndividualCall.Direction.INCOMING
|
||||
)
|
||||
)
|
||||
}
|
||||
MessageTypes.isOutgoingAudioCall(record.type) -> {
|
||||
builder.updateMessage = ChatUpdateMessage(callingMessage = CallChatUpdate(callMessage = IndividualCallChatUpdate(type = IndividualCallChatUpdate.Type.OUTGOING_AUDIO_CALL)))
|
||||
builder.updateMessage = ChatUpdateMessage(
|
||||
individualCall = IndividualCall(
|
||||
type = IndividualCall.Type.AUDIO_CALL,
|
||||
state = IndividualCall.State.ACCEPTED,
|
||||
direction = IndividualCall.Direction.OUTGOING
|
||||
)
|
||||
)
|
||||
}
|
||||
MessageTypes.isOutgoingVideoCall(record.type) -> {
|
||||
builder.updateMessage = ChatUpdateMessage(callingMessage = CallChatUpdate(callMessage = IndividualCallChatUpdate(type = IndividualCallChatUpdate.Type.OUTGOING_VIDEO_CALL)))
|
||||
builder.updateMessage = ChatUpdateMessage(
|
||||
individualCall = IndividualCall(
|
||||
type = IndividualCall.Type.VIDEO_CALL,
|
||||
state = IndividualCall.State.ACCEPTED,
|
||||
direction = IndividualCall.Direction.OUTGOING
|
||||
)
|
||||
)
|
||||
}
|
||||
MessageTypes.isGroupCall(record.type) -> {
|
||||
try {
|
||||
val groupCallUpdateDetails = GroupCallUpdateDetailsUtil.parse(record.body)
|
||||
|
||||
val joinedMembers = Stream.of(groupCallUpdateDetails.inCallUuids)
|
||||
.map { uuid: String? -> UuidUtil.parseOrNull(uuid) }
|
||||
.withoutNulls()
|
||||
.map { obj: UUID? -> ACI.from(obj!!).toByteString() }
|
||||
.toList()
|
||||
|
||||
val localUserJoined: GroupCallChatUpdate.LocalUserJoined = if (groupCallUpdateDetails.localUserJoined) {
|
||||
GroupCallChatUpdate.LocalUserJoined.JOINED
|
||||
} else if (groupCallUpdateDetails.endedCallTimestamp == 0L) {
|
||||
GroupCallChatUpdate.LocalUserJoined.UNKNOWN
|
||||
} else {
|
||||
GroupCallChatUpdate.LocalUserJoined.DID_NOT_JOIN
|
||||
}
|
||||
|
||||
builder.updateMessage = ChatUpdateMessage(
|
||||
callingMessage = CallChatUpdate(
|
||||
groupCall = GroupCallChatUpdate(
|
||||
startedCallAci = ACI.from(UuidUtil.parseOrThrow(groupCallUpdateDetails.startedCallUuid)).toByteString(),
|
||||
startedCallTimestamp = groupCallUpdateDetails.startedCallTimestamp,
|
||||
inCallAcis = joinedMembers,
|
||||
localUserJoined = localUserJoined,
|
||||
endedCallTimestamp = groupCallUpdateDetails.endedCallTimestamp
|
||||
)
|
||||
groupCall = GroupCall(
|
||||
state = GroupCall.State.GENERIC,
|
||||
startedCallAci = ACI.from(UuidUtil.parseOrThrow(groupCallUpdateDetails.startedCallUuid)).toByteString(),
|
||||
startedCallTimestamp = groupCallUpdateDetails.startedCallTimestamp,
|
||||
endedCallTimestamp = groupCallUpdateDetails.endedCallTimestamp
|
||||
)
|
||||
)
|
||||
} catch (exception: java.lang.Exception) {
|
||||
@@ -298,12 +354,13 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
||||
chatId = record.threadId
|
||||
authorId = record.fromRecipientId
|
||||
dateSent = record.dateSent
|
||||
expireStartDate = if (record.expireStarted > 0) record.expireStarted else null
|
||||
expiresInMs = if (record.expiresIn > 0) record.expiresIn else null
|
||||
expireStartDate = if (record.expireStarted > 0) record.expireStarted else 0
|
||||
expiresInMs = if (record.expiresIn > 0) record.expiresIn else 0
|
||||
revisions = emptyList()
|
||||
sms = !MessageTypes.isSecureType(record.type)
|
||||
|
||||
if (MessageTypes.isOutgoingMessageType(record.type)) {
|
||||
if (MessageTypes.isCallLog(record.type)) {
|
||||
directionless = ChatItem.DirectionlessMessageDetails()
|
||||
} else if (MessageTypes.isOutgoingMessageType(record.type)) {
|
||||
outgoing = ChatItem.OutgoingMessageDetails(
|
||||
sendStatus = record.toBackupSendStatus(groupReceipts)
|
||||
)
|
||||
|
||||
@@ -22,7 +22,8 @@ import org.thoughtcrime.securesms.backup.v2.BackupState
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.BodyRange
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatUpdateMessage
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.IndividualCallChatUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupCall
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.IndividualCall
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.MessageAttachment
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Quote
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Reaction
|
||||
@@ -215,11 +216,53 @@ class ChatItemImportInserter(
|
||||
|
||||
var followUp: ((Long) -> Unit)? = null
|
||||
if (this.updateMessage != null) {
|
||||
if (this.updateMessage.callingMessage != null && this.updateMessage.callingMessage.callId != null) {
|
||||
if (this.updateMessage.individualCall != null && this.updateMessage.individualCall.callId != null) {
|
||||
followUp = { messageRowId ->
|
||||
val callContentValues = ContentValues()
|
||||
callContentValues.put(CallTable.MESSAGE_ID, messageRowId)
|
||||
db.update(CallTable.TABLE_NAME, SQLiteDatabase.CONFLICT_IGNORE, callContentValues, "${CallTable.CALL_ID} = ?", SqlUtil.buildArgs(this.updateMessage.callingMessage.callId))
|
||||
val values = contentValuesOf(
|
||||
CallTable.CALL_ID to updateMessage.individualCall.callId,
|
||||
CallTable.MESSAGE_ID to messageRowId,
|
||||
CallTable.PEER to chatRecipientId.serialize(),
|
||||
CallTable.TYPE to CallTable.Type.serialize(if (updateMessage.individualCall.type == IndividualCall.Type.VIDEO_CALL) CallTable.Type.VIDEO_CALL else CallTable.Type.AUDIO_CALL),
|
||||
CallTable.DIRECTION to CallTable.Direction.serialize(if (updateMessage.individualCall.direction == IndividualCall.Direction.OUTGOING) CallTable.Direction.OUTGOING else CallTable.Direction.INCOMING),
|
||||
CallTable.EVENT to CallTable.Event.serialize(
|
||||
when (updateMessage.individualCall.state) {
|
||||
IndividualCall.State.MISSED -> CallTable.Event.MISSED
|
||||
IndividualCall.State.MISSED_NOTIFICATION_PROFILE -> CallTable.Event.MISSED_NOTIFICATION_PROFILE
|
||||
IndividualCall.State.ACCEPTED -> CallTable.Event.ACCEPTED
|
||||
IndividualCall.State.NOT_ACCEPTED -> CallTable.Event.NOT_ACCEPTED
|
||||
else -> CallTable.Event.MISSED
|
||||
}
|
||||
),
|
||||
CallTable.TIMESTAMP to updateMessage.individualCall.startedCallTimestamp,
|
||||
CallTable.READ to CallTable.ReadState.serialize(CallTable.ReadState.UNREAD)
|
||||
)
|
||||
db.insert(CallTable.TABLE_NAME, SQLiteDatabase.CONFLICT_IGNORE, values)
|
||||
}
|
||||
} else if (this.updateMessage.groupCall != null && this.updateMessage.groupCall.callId != null) {
|
||||
followUp = { messageRowId ->
|
||||
val values = contentValuesOf(
|
||||
CallTable.CALL_ID to updateMessage.groupCall.callId,
|
||||
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.EVENT to CallTable.Event.serialize(
|
||||
when (updateMessage.groupCall.state) {
|
||||
GroupCall.State.ACCEPTED -> CallTable.Event.ACCEPTED
|
||||
GroupCall.State.MISSED -> CallTable.Event.MISSED
|
||||
GroupCall.State.MISSED_NOTIFICATION_PROFILE -> CallTable.Event.MISSED_NOTIFICATION_PROFILE
|
||||
GroupCall.State.GENERIC -> CallTable.Event.GENERIC_GROUP_CALL
|
||||
GroupCall.State.JOINED -> CallTable.Event.JOINED
|
||||
GroupCall.State.RINGING -> CallTable.Event.RINGING
|
||||
GroupCall.State.OUTGOING_RING -> CallTable.Event.OUTGOING_RING
|
||||
GroupCall.State.DECLINED -> CallTable.Event.DECLINED
|
||||
else -> CallTable.Event.GENERIC_GROUP_CALL
|
||||
}
|
||||
),
|
||||
CallTable.TIMESTAMP to updateMessage.groupCall.startedCallTimestamp,
|
||||
CallTable.READ to CallTable.ReadState.serialize(CallTable.ReadState.UNREAD)
|
||||
)
|
||||
db.insert(CallTable.TABLE_NAME, SQLiteDatabase.CONFLICT_IGNORE, values)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -444,32 +487,22 @@ class ChatItemImportInserter(
|
||||
val threadMergeDetails = ThreadMergeEvent(previousE164 = updateMessage.threadMerge.previousE164.toString()).encode()
|
||||
put(MessageTable.BODY, Base64.encodeWithPadding(threadMergeDetails))
|
||||
}
|
||||
updateMessage.callingMessage != null -> {
|
||||
when {
|
||||
updateMessage.callingMessage.callId != null -> {
|
||||
typeFlags = backupState.callIdToType[updateMessage.callingMessage.callId]!!
|
||||
}
|
||||
updateMessage.callingMessage.callMessage != null -> {
|
||||
typeFlags = when (updateMessage.callingMessage.callMessage.type) {
|
||||
IndividualCallChatUpdate.Type.INCOMING_AUDIO_CALL -> MessageTypes.INCOMING_AUDIO_CALL_TYPE
|
||||
IndividualCallChatUpdate.Type.INCOMING_VIDEO_CALL -> MessageTypes.INCOMING_VIDEO_CALL_TYPE
|
||||
IndividualCallChatUpdate.Type.OUTGOING_AUDIO_CALL -> MessageTypes.OUTGOING_AUDIO_CALL_TYPE
|
||||
IndividualCallChatUpdate.Type.OUTGOING_VIDEO_CALL -> MessageTypes.OUTGOING_VIDEO_CALL_TYPE
|
||||
IndividualCallChatUpdate.Type.MISSED_INCOMING_AUDIO_CALL -> MessageTypes.MISSED_AUDIO_CALL_TYPE
|
||||
IndividualCallChatUpdate.Type.MISSED_INCOMING_VIDEO_CALL -> MessageTypes.MISSED_VIDEO_CALL_TYPE
|
||||
IndividualCallChatUpdate.Type.UNANSWERED_OUTGOING_AUDIO_CALL -> MessageTypes.OUTGOING_AUDIO_CALL_TYPE
|
||||
IndividualCallChatUpdate.Type.UNANSWERED_OUTGOING_VIDEO_CALL -> MessageTypes.OUTGOING_VIDEO_CALL_TYPE
|
||||
IndividualCallChatUpdate.Type.UNKNOWN -> typeFlags
|
||||
}
|
||||
}
|
||||
updateMessage.callingMessage.groupCall != null -> {
|
||||
typeFlags = MessageTypes.GROUP_CALL_TYPE
|
||||
this.put(MessageTable.BODY, GroupCallUpdateDetailsUtil.createBodyFromBackup(updateMessage.callingMessage.groupCall))
|
||||
updateMessage.individualCall != null -> {
|
||||
if (updateMessage.individualCall.state == IndividualCall.State.MISSED || updateMessage.individualCall.state == IndividualCall.State.MISSED_NOTIFICATION_PROFILE) {
|
||||
typeFlags = if (updateMessage.individualCall.type == IndividualCall.Type.AUDIO_CALL) MessageTypes.MISSED_AUDIO_CALL_TYPE else MessageTypes.MISSED_VIDEO_CALL_TYPE
|
||||
} else {
|
||||
typeFlags = if (updateMessage.individualCall.direction == IndividualCall.Direction.OUTGOING) {
|
||||
if (updateMessage.individualCall.type == IndividualCall.Type.AUDIO_CALL) MessageTypes.OUTGOING_AUDIO_CALL_TYPE else MessageTypes.OUTGOING_VIDEO_CALL_TYPE
|
||||
} else {
|
||||
if (updateMessage.individualCall.type == IndividualCall.Type.AUDIO_CALL) MessageTypes.INCOMING_AUDIO_CALL_TYPE else MessageTypes.INCOMING_VIDEO_CALL_TYPE
|
||||
}
|
||||
}
|
||||
// Calls don't use the incoming/outgoing flags, so we overwrite the flags here
|
||||
this.put(MessageTable.TYPE, typeFlags)
|
||||
}
|
||||
updateMessage.groupCall != null -> {
|
||||
this.put(MessageTable.BODY, GroupCallUpdateDetailsUtil.createBodyFromBackup(updateMessage.groupCall))
|
||||
this.put(MessageTable.TYPE, MessageTypes.GROUP_CALL_TYPE)
|
||||
}
|
||||
updateMessage.groupChange != null -> {
|
||||
put(MessageTable.BODY, "")
|
||||
put(
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.signal.storageservice.protos.groups.local.DecryptedPendingMember
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedTimer
|
||||
import org.signal.storageservice.protos.groups.local.EnabledState
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupState
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.AccountData
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Contact
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Group
|
||||
@@ -47,6 +46,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.RecipientExtras
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName
|
||||
@@ -129,25 +129,6 @@ fun RecipientTable.getGroupsForBackup(): BackupGroupIterator {
|
||||
return BackupGroupIterator(cursor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a [BackupRecipient] and writes it into the database.
|
||||
*/
|
||||
fun RecipientTable.restoreRecipientFromBackup(recipient: BackupRecipient, backupState: BackupState): RecipientId? {
|
||||
// TODO Need to handle groups
|
||||
// TODO Also, should we move this when statement up to mimic the export? Kinda weird that this calls distributionListTable functions
|
||||
return when {
|
||||
recipient.contact != null -> restoreContactFromBackup(recipient.contact)
|
||||
recipient.group != null -> restoreGroupFromBackup(recipient.group)
|
||||
recipient.distributionList != null -> SignalDatabase.distributionLists.restoreFromBackup(recipient.distributionList, backupState)
|
||||
recipient.self != null -> Recipient.self().id
|
||||
recipient.releaseNotes != null -> restoreReleaseNotes()
|
||||
else -> {
|
||||
Log.w(TAG, "Unrecognized recipient type!")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given [AccountData], this will insert the necessary data for the local user into the [RecipientTable].
|
||||
*/
|
||||
@@ -187,7 +168,7 @@ fun RecipientTable.clearAllDataForBackupRestore() {
|
||||
ApplicationDependencies.getRecipientCache().clearSelf()
|
||||
}
|
||||
|
||||
private fun RecipientTable.restoreContactFromBackup(contact: Contact): RecipientId {
|
||||
fun RecipientTable.restoreContactFromBackup(contact: Contact): RecipientId {
|
||||
val id = getAndPossiblyMergePnpVerified(
|
||||
aci = ACI.parseOrNull(contact.aci?.toByteArray()),
|
||||
pni = PNI.parseOrNull(contact.pni?.toByteArray()),
|
||||
@@ -218,7 +199,7 @@ private fun RecipientTable.restoreContactFromBackup(contact: Contact): Recipient
|
||||
return id
|
||||
}
|
||||
|
||||
private fun RecipientTable.restoreReleaseNotes(): RecipientId {
|
||||
fun RecipientTable.restoreReleaseNotes(): RecipientId {
|
||||
val releaseChannelId: RecipientId = insertReleaseChannelRecipient()
|
||||
SignalStore.releaseChannelValues().setReleaseChannelRecipientId(releaseChannelId)
|
||||
|
||||
@@ -227,12 +208,16 @@ private fun RecipientTable.restoreReleaseNotes(): RecipientId {
|
||||
return releaseChannelId
|
||||
}
|
||||
|
||||
private fun RecipientTable.restoreGroupFromBackup(group: Group): RecipientId {
|
||||
fun RecipientTable.restoreGroupFromBackup(group: Group): RecipientId {
|
||||
val masterKey = GroupMasterKey(group.masterKey.toByteArray())
|
||||
val groupId = GroupId.v2(masterKey)
|
||||
|
||||
val operations = ApplicationDependencies.getGroupsV2Operations().forGroup(GroupSecretParams.deriveFromMasterKey(masterKey))
|
||||
val decryptedState = group.snapshot!!.toDecryptedGroup(operations)
|
||||
val decryptedState = if (group.snapshot == null) {
|
||||
DecryptedGroup(revision = GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION)
|
||||
} else {
|
||||
group.snapshot.toDecryptedGroup(operations)
|
||||
}
|
||||
|
||||
val values = ContentValues().apply {
|
||||
put(RecipientTable.GROUP_ID, groupId.toString())
|
||||
@@ -296,7 +281,10 @@ private fun Member.Role.toSnapshot(): Group.Member.Role {
|
||||
}
|
||||
}
|
||||
|
||||
private fun DecryptedGroup.toSnapshot(): Group.GroupSnapshot {
|
||||
private fun DecryptedGroup.toSnapshot(): Group.GroupSnapshot? {
|
||||
if (revision == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION || revision == GroupsV2StateProcessor.PLACEHOLDER_REVISION) {
|
||||
return null
|
||||
}
|
||||
return Group.GroupSnapshot(
|
||||
title = title,
|
||||
avatar = avatar,
|
||||
@@ -479,7 +467,6 @@ class BackupGroupIterator(private val cursor: Cursor) : Iterator<BackupRecipient
|
||||
whitelisted = cursor.requireBoolean(RecipientTable.PROFILE_SHARING),
|
||||
hideStory = extras?.hideStory() ?: false,
|
||||
storySendMode = showAsStoryState.toGroupStorySendMode(),
|
||||
name = cursor.requireString(GroupTable.TITLE) ?: "",
|
||||
snapshot = decryptedGroup.toSnapshot()
|
||||
)
|
||||
)
|
||||
|
||||
@@ -7,29 +7,28 @@ 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.database.getCallsForBackup
|
||||
import org.thoughtcrime.securesms.backup.v2.database.getAdhocCallsForBackup
|
||||
import org.thoughtcrime.securesms.backup.v2.database.restoreCallLogFromBackup
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.AdHocCall
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Frame
|
||||
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
|
||||
typealias BackupCall = org.thoughtcrime.securesms.backup.v2.proto.Call
|
||||
object AdHocCallBackupProcessor {
|
||||
|
||||
object CallLogBackupProcessor {
|
||||
|
||||
val TAG = Log.tag(CallLogBackupProcessor::class.java)
|
||||
val TAG = Log.tag(AdHocCallBackupProcessor::class.java)
|
||||
|
||||
fun export(emitter: BackupFrameEmitter) {
|
||||
SignalDatabase.calls.getCallsForBackup().use { reader ->
|
||||
SignalDatabase.calls.getAdhocCallsForBackup().use { reader ->
|
||||
for (callLog in reader) {
|
||||
if (callLog != null) {
|
||||
emitter.emit(Frame(call = callLog))
|
||||
emitter.emit(Frame(adHocCall = callLog))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun import(call: BackupCall, backupState: BackupState) {
|
||||
fun import(call: AdHocCall, backupState: BackupState) {
|
||||
SignalDatabase.calls.restoreCallLogFromBackup(call, backupState)
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,13 @@ import org.thoughtcrime.securesms.backup.v2.BackupState
|
||||
import org.thoughtcrime.securesms.backup.v2.ExportState
|
||||
import org.thoughtcrime.securesms.backup.v2.database.BackupRecipient
|
||||
import org.thoughtcrime.securesms.backup.v2.database.getAllForBackup
|
||||
import org.thoughtcrime.securesms.backup.v2.database.getCallLinksForBackup
|
||||
import org.thoughtcrime.securesms.backup.v2.database.getContactsForBackup
|
||||
import org.thoughtcrime.securesms.backup.v2.database.getGroupsForBackup
|
||||
import org.thoughtcrime.securesms.backup.v2.database.restoreRecipientFromBackup
|
||||
import org.thoughtcrime.securesms.backup.v2.database.restoreContactFromBackup
|
||||
import org.thoughtcrime.securesms.backup.v2.database.restoreFromBackup
|
||||
import org.thoughtcrime.securesms.backup.v2.database.restoreGroupFromBackup
|
||||
import org.thoughtcrime.securesms.backup.v2.database.restoreReleaseNotes
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Frame
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ReleaseNotes
|
||||
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
|
||||
@@ -60,10 +64,26 @@ object RecipientBackupProcessor {
|
||||
state.recipientIds.add(it.id)
|
||||
emitter.emit(Frame(recipient = it))
|
||||
}
|
||||
|
||||
SignalDatabase.callLinks.getCallLinksForBackup().forEach {
|
||||
state.recipientIds.add(it.id)
|
||||
emitter.emit(Frame(recipient = it))
|
||||
}
|
||||
}
|
||||
|
||||
fun import(recipient: BackupRecipient, backupState: BackupState) {
|
||||
val newId = SignalDatabase.recipients.restoreRecipientFromBackup(recipient, backupState)
|
||||
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.self != null -> Recipient.self().id
|
||||
recipient.releaseNotes != null -> SignalDatabase.recipients.restoreReleaseNotes()
|
||||
recipient.callLink != null -> SignalDatabase.callLinks.restoreFromBackup(recipient.callLink)
|
||||
else -> {
|
||||
Log.w(TAG, "Unrecognized recipient type!")
|
||||
null
|
||||
}
|
||||
}
|
||||
if (newId != null) {
|
||||
backupState.backupToLocalRecipientId[recipient.id] = newId
|
||||
}
|
||||
|
||||
@@ -91,18 +91,22 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
|
||||
|
||||
fun insertCallLink(
|
||||
callLink: CallLink
|
||||
) {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
): RecipientId {
|
||||
val recipientId: RecipientId = writableDatabase.withinTransaction { db ->
|
||||
val recipientId = SignalDatabase.recipients.getOrInsertFromCallLinkRoomId(callLink.roomId)
|
||||
|
||||
db
|
||||
.insertInto(TABLE_NAME)
|
||||
.values(CallLinkSerializer.serialize(callLink.copy(recipientId = recipientId)))
|
||||
.run()
|
||||
|
||||
recipientId
|
||||
}
|
||||
|
||||
ApplicationDependencies.getDatabaseObserver().notifyCallLinkObservers(callLink.roomId)
|
||||
ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers()
|
||||
|
||||
return recipientId!!
|
||||
}
|
||||
|
||||
fun updateCallLinkCredentials(
|
||||
@@ -402,7 +406,7 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
|
||||
}
|
||||
}
|
||||
|
||||
private object CallLinkDeserializer : Serializer<CallLink, Cursor> {
|
||||
object CallLinkDeserializer : Serializer<CallLink, Cursor> {
|
||||
override fun serialize(data: CallLink): Cursor {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.Base64;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupCallChatUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupCall;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateDetails;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
@@ -28,7 +28,7 @@ public final class GroupCallUpdateDetailsUtil {
|
||||
/**
|
||||
* Generates a group chat update message body from backup data
|
||||
*/
|
||||
public static @NonNull String createBodyFromBackup(@NonNull GroupCallChatUpdate groupCallChatUpdate) {
|
||||
public static @NonNull String createBodyFromBackup(@NonNull GroupCall groupCallChatUpdate) {
|
||||
ServiceId.ACI startedCall = groupCallChatUpdate.startedCallAci != null ? ServiceId.ACI.parseOrNull(groupCallChatUpdate.startedCallAci) : null;
|
||||
|
||||
GroupCallUpdateDetails details = new GroupCallUpdateDetails.Builder()
|
||||
@@ -36,15 +36,7 @@ public final class GroupCallUpdateDetailsUtil {
|
||||
.startedCallTimestamp(groupCallChatUpdate.startedCallTimestamp)
|
||||
.endedCallTimestamp(groupCallChatUpdate.endedCallTimestamp)
|
||||
.isCallFull(false)
|
||||
.inCallUuids(groupCallChatUpdate.inCallAcis.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(ServiceId.ACI::parseOrNull)
|
||||
.filter(Objects::nonNull)
|
||||
.map(ServiceId.ACI::toString)
|
||||
.collect(Collectors.toList())
|
||||
)
|
||||
.isRingingOnLocalDevice(false)
|
||||
.localUserJoined(groupCallChatUpdate.localUserJoined != GroupCallChatUpdate.LocalUserJoined.DID_NOT_JOIN)
|
||||
.build();
|
||||
|
||||
return Base64.encodeWithPadding(details.encode());
|
||||
|
||||
Reference in New Issue
Block a user