Add Receive support for the new CallLogEvent proto messages.
This commit is contained in:
parent
461875b0e4
commit
a8349671d0
8 changed files with 260 additions and 20 deletions
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import junit.framework.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.thoughtcrime.securesms.calls.log.CallLogFilter
|
||||
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 org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CallLinkTableTest {
|
||||
|
||||
companion object {
|
||||
private val ROOM_ID_A = byteArrayOf(1, 2, 3, 4)
|
||||
private val ROOM_ID_B = byteArrayOf(2, 2, 3, 4)
|
||||
private const val TIMESTAMP_A = 1000L
|
||||
private const val TIMESTAMP_B = 2000L
|
||||
}
|
||||
|
||||
@get:Rule
|
||||
val harness = SignalActivityRule(createGroup = true)
|
||||
|
||||
@Test
|
||||
fun givenTwoNonAdminCallLinks_whenIDeleteBeforeFirst_thenIExpectNeitherDeleted() {
|
||||
insertTwoNonAdminCallLinksWithEvents()
|
||||
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_A - 500)
|
||||
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
|
||||
assertEquals(2, callEvents.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoNonAdminCallLinks_whenIDeleteOnFirst_thenIExpectFirstDeleted() {
|
||||
insertTwoNonAdminCallLinksWithEvents()
|
||||
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_A)
|
||||
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
|
||||
assertEquals(1, callEvents.size)
|
||||
assertEquals(TIMESTAMP_B, callEvents.first().record.timestamp)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoNonAdminCallLinks_whenIDeleteAfterFirstAndBeforeSecond_thenIExpectFirstDeleted() {
|
||||
insertTwoNonAdminCallLinksWithEvents()
|
||||
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_B - 500)
|
||||
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
|
||||
assertEquals(1, callEvents.size)
|
||||
assertEquals(TIMESTAMP_B, callEvents.first().record.timestamp)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoNonAdminCallLinks_whenIDeleteOnSecond_thenIExpectBothDeleted() {
|
||||
insertTwoNonAdminCallLinksWithEvents()
|
||||
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_B)
|
||||
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
|
||||
assertEquals(0, callEvents.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoNonAdminCallLinks_whenIDeleteAfterSecond_thenIExpectBothDeleted() {
|
||||
insertTwoNonAdminCallLinksWithEvents()
|
||||
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_B + 500)
|
||||
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
|
||||
assertEquals(0, callEvents.size)
|
||||
}
|
||||
|
||||
private fun insertTwoNonAdminCallLinksWithEvents() {
|
||||
insertCallLinkWithEvent(ROOM_ID_A, 1000)
|
||||
insertCallLinkWithEvent(ROOM_ID_B, 2000)
|
||||
}
|
||||
|
||||
private fun insertCallLinkWithEvent(roomId: ByteArray, timestamp: Long) {
|
||||
SignalDatabase.callLinks.insertCallLink(
|
||||
CallLinkTable.CallLink(
|
||||
recipientId = RecipientId.UNKNOWN,
|
||||
roomId = CallLinkRoomId.fromBytes(roomId),
|
||||
credentials = CallLinkCredentials(
|
||||
linkKeyBytes = roomId,
|
||||
adminPassBytes = null
|
||||
),
|
||||
state = SignalCallLinkState()
|
||||
)
|
||||
)
|
||||
|
||||
val callLinkRecipient = SignalDatabase.recipients.getByCallLinkRoomId(CallLinkRoomId.fromBytes(roomId)).get()
|
||||
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
1,
|
||||
callLinkRecipient,
|
||||
CallTable.Direction.INCOMING,
|
||||
timestamp
|
||||
)
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import org.junit.Test
|
|||
import org.junit.runner.RunWith
|
||||
import org.signal.ringrtc.CallId
|
||||
import org.signal.ringrtc.CallManager
|
||||
import org.thoughtcrime.securesms.calls.log.CallLogFilter
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
|
||||
|
@ -751,4 +752,74 @@ class CallTableTest {
|
|||
assertEquals(CallTable.Event.DECLINED, call?.event)
|
||||
assertNotNull(call?.messageId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoCalls_whenIDeleteBeforeCallB_thenOnlyDeleteCallA() {
|
||||
insertTwoCallEvents()
|
||||
|
||||
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(1500)
|
||||
|
||||
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
|
||||
assertEquals(1, allCallEvents.size)
|
||||
assertEquals(2, allCallEvents.first().record.callId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoCalls_whenIDeleteBeforeCallA_thenIDoNotDeleteAnyCalls() {
|
||||
insertTwoCallEvents()
|
||||
|
||||
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(500)
|
||||
|
||||
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
|
||||
assertEquals(2, allCallEvents.size)
|
||||
assertEquals(2, allCallEvents[0].record.callId)
|
||||
assertEquals(1, allCallEvents[1].record.callId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoCalls_whenIDeleteOnCallA_thenIOnlyDeleteCallA() {
|
||||
insertTwoCallEvents()
|
||||
|
||||
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(1000)
|
||||
|
||||
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
|
||||
assertEquals(1, allCallEvents.size)
|
||||
assertEquals(2, allCallEvents.first().record.callId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoCalls_whenIDeleteOnCallB_thenIDeleteBothCalls() {
|
||||
insertTwoCallEvents()
|
||||
|
||||
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(2000)
|
||||
|
||||
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
|
||||
assertEquals(0, allCallEvents.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoCalls_whenIDeleteAfterCallB_thenIDeleteBothCalls() {
|
||||
insertTwoCallEvents()
|
||||
|
||||
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(2500)
|
||||
|
||||
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
|
||||
assertEquals(0, allCallEvents.size)
|
||||
}
|
||||
|
||||
private fun insertTwoCallEvents() {
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
1,
|
||||
groupRecipientId,
|
||||
CallTable.Direction.INCOMING,
|
||||
1000
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
2,
|
||||
groupRecipientId,
|
||||
CallTable.Direction.OUTGOING,
|
||||
2000
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.signal.core.util.delete
|
|||
import org.signal.core.util.insertInto
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.readToList
|
||||
import org.signal.core.util.readToSet
|
||||
import org.signal.core.util.readToSingleInt
|
||||
import org.signal.core.util.readToSingleObject
|
||||
import org.signal.core.util.requireBlob
|
||||
|
@ -226,6 +227,16 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
|
|||
}
|
||||
}
|
||||
|
||||
fun deleteNonAdminCallLinksOnOrBefore(timestamp: Long) {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db.delete(TABLE_NAME)
|
||||
.where("EXISTS (SELECT 1 FROM ${CallTable.TABLE_NAME} WHERE ${CallTable.TIMESTAMP} <= ? AND ${CallTable.PEER} = $RECIPIENT_ID)", timestamp)
|
||||
.run()
|
||||
|
||||
SignalDatabase.calls.updateAdHocCallEventDeletionTimestamps(skipSync = true)
|
||||
}
|
||||
}
|
||||
|
||||
fun getAdminCallLinks(roomIds: Set<CallLinkRoomId>): Set<CallLink> {
|
||||
val queries = SqlUtil.buildCollectionQuery(ROOM_ID, roomIds)
|
||||
|
||||
|
@ -274,6 +285,18 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
|
|||
}
|
||||
}
|
||||
|
||||
fun getAdminCallLinkCredentialsOnOrBefore(timestamp: Long): Set<CallLinkCredentials> {
|
||||
val query = """
|
||||
SELECT $ROOT_KEY, $ADMIN_KEY FROM $TABLE_NAME
|
||||
INNER JOIN ${CallTable.TABLE_NAME} ON ${CallTable.TABLE_NAME}.${CallTable.PEER} = $TABLE_NAME.$RECIPIENT_ID
|
||||
WHERE ${CallTable.TIMESTAMP} <= $timestamp AND $ADMIN_KEY IS NOT NULL AND $REVOKED = 0
|
||||
""".trimIndent()
|
||||
|
||||
return readableDatabase.query(query).readToSet {
|
||||
CallLinkCredentials(it.requireNonNullBlob(ROOT_KEY), it.requireNonNullBlob(ADMIN_KEY))
|
||||
}
|
||||
}
|
||||
|
||||
private fun queryCallLinks(query: String?, offset: Int, limit: Int, asCount: Boolean): Cursor {
|
||||
//language=sql
|
||||
val noCallEvent = """
|
||||
|
|
|
@ -56,7 +56,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
const val TYPE = "type"
|
||||
private const val DIRECTION = "direction"
|
||||
const val EVENT = "event"
|
||||
private const val TIMESTAMP = "timestamp"
|
||||
const val TIMESTAMP = "timestamp"
|
||||
private const val RINGER = "ringer"
|
||||
private const val DELETION_TIMESTAMP = "deletion_timestamp"
|
||||
|
||||
|
@ -227,7 +227,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
* If a call link has been revoked, or if we do not have a CallLink table entry for an AD_HOC_CALL type
|
||||
* event, we mark it deleted.
|
||||
*/
|
||||
fun updateAdHocCallEventDeletionTimestamps() {
|
||||
fun updateAdHocCallEventDeletionTimestamps(skipSync: Boolean = false) {
|
||||
//language=sql
|
||||
val statement = """
|
||||
UPDATE $TABLE_NAME
|
||||
|
@ -245,7 +245,10 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
Call.deserialize(it)
|
||||
}.toSet()
|
||||
|
||||
CallSyncEventJob.enqueueDeleteSyncEvents(toSync)
|
||||
if (!skipSync) {
|
||||
CallSyncEventJob.enqueueDeleteSyncEvents(toSync)
|
||||
}
|
||||
|
||||
ApplicationDependencies.getDeletedCallEventManager().scheduleIfNecessary()
|
||||
ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers()
|
||||
}
|
||||
|
@ -254,7 +257,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
* If a non-ad-hoc call has been deleted from the message database, then we need to
|
||||
* set its deletion_timestamp to now.
|
||||
*/
|
||||
fun updateCallEventDeletionTimestamps() {
|
||||
fun updateCallEventDeletionTimestamps(skipSync: Boolean = false) {
|
||||
val where = "$TYPE != ? AND $DELETION_TIMESTAMP = 0 AND $MESSAGE_ID IS NULL"
|
||||
val type = Type.serialize(Type.AD_HOC_CALL)
|
||||
|
||||
|
@ -281,7 +284,10 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
result
|
||||
}
|
||||
|
||||
CallSyncEventJob.enqueueDeleteSyncEvents(toSync)
|
||||
if (!skipSync) {
|
||||
CallSyncEventJob.enqueueDeleteSyncEvents(toSync)
|
||||
}
|
||||
|
||||
ApplicationDependencies.getDeletedCallEventManager().scheduleIfNecessary()
|
||||
ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers()
|
||||
}
|
||||
|
@ -800,6 +806,20 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
.run()
|
||||
}
|
||||
|
||||
fun deleteNonAdHocCallEventsOnOrBefore(timestamp: Long) {
|
||||
val messageIdsOnOrBeforeTimestamp = """
|
||||
SELECT $MESSAGE_ID FROM $TABLE_NAME WHERE $TIMESTAMP <= $timestamp AND $MESSAGE_ID IS NOT NULL
|
||||
""".trimIndent()
|
||||
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db.delete(MessageTable.TABLE_NAME)
|
||||
.where("${MessageTable.ID} IN ($messageIdsOnOrBeforeTimestamp)")
|
||||
.run()
|
||||
|
||||
updateCallEventDeletionTimestamps(skipSync = true)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteNonAdHocCallEvents(callRowIds: Set<Long>) {
|
||||
val messageIds = getMessageIds(callRowIds)
|
||||
SignalDatabase.messages.deleteCallUpdates(messageIds)
|
||||
|
|
|
@ -108,6 +108,7 @@ import org.whispersystems.signalservice.internal.push.SignalServiceProtos.StoryM
|
|||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.Blocked
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.CallLinkUpdate
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.CallLogEvent
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.Configuration
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.FetchLatest
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.MessageRequestResponse
|
||||
|
@ -150,6 +151,7 @@ object SyncMessageProcessor {
|
|||
syncMessage.hasContacts() -> handleSynchronizeContacts(syncMessage.contacts, envelope.timestamp)
|
||||
syncMessage.hasCallEvent() -> handleSynchronizeCallEvent(syncMessage.callEvent, envelope.timestamp)
|
||||
syncMessage.hasCallLinkUpdate() -> handleSynchronizeCallLink(syncMessage.callLinkUpdate, envelope.timestamp)
|
||||
syncMessage.hasCallLogEvent() -> handleSynchronizeCallLogEvent(syncMessage.callLogEvent, envelope.timestamp)
|
||||
else -> warn(envelope.timestamp, "Contains no known sync types...")
|
||||
}
|
||||
}
|
||||
|
@ -1162,6 +1164,16 @@ object SyncMessageProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleSynchronizeCallLogEvent(callLogEvent: CallLogEvent, envelopeTimestamp: Long) {
|
||||
if (callLogEvent.type != CallLogEvent.Type.CLEAR) {
|
||||
log(envelopeTimestamp, "Synchronize call log event has an invalid type ${callLogEvent.type}, ignoring.")
|
||||
return
|
||||
}
|
||||
|
||||
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(callLogEvent.timestamp)
|
||||
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(callLogEvent.timestamp)
|
||||
}
|
||||
|
||||
private fun handleSynchronizeCallLink(callLinkUpdate: CallLinkUpdate, envelopeTimestamp: Long) {
|
||||
if (!callLinkUpdate.hasRootKey()) {
|
||||
log(envelopeTimestamp, "Synchronize call link missing root key, ignoring.")
|
||||
|
@ -1185,21 +1197,20 @@ object SyncMessageProcessor {
|
|||
callLinkUpdate.adminPassKey?.toByteArray()
|
||||
)
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
SignalDatabase.callLinks.insertCallLink(
|
||||
CallLinkTable.CallLink(
|
||||
recipientId = RecipientId.UNKNOWN,
|
||||
roomId = roomId,
|
||||
credentials = CallLinkCredentials(
|
||||
linkKeyBytes = callLinkRootKey.keyBytes,
|
||||
adminPassBytes = callLinkUpdate.adminPassKey?.toByteArray()
|
||||
),
|
||||
state = SignalCallLinkState()
|
||||
} else {
|
||||
log(envelopeTimestamp, "Synchronize call link for a link we do not know about. Inserting.")
|
||||
SignalDatabase.callLinks.insertCallLink(
|
||||
CallLinkTable.CallLink(
|
||||
recipientId = RecipientId.UNKNOWN,
|
||||
roomId = roomId,
|
||||
credentials = CallLinkCredentials(
|
||||
linkKeyBytes = callLinkRootKey.keyBytes,
|
||||
adminPassBytes = callLinkUpdate.adminPassKey?.toByteArray()
|
||||
),
|
||||
state = SignalCallLinkState()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
ApplicationDependencies.getJobManager().add(RefreshCallLinkDetailsJob(callLinkUpdate))
|
||||
}
|
||||
|
|
|
@ -137,7 +137,6 @@ class SignalCallLinkManager(
|
|||
credentials: CallLinkCredentials
|
||||
): Single<ReadCallLinkResult> {
|
||||
return Single.create { emitter ->
|
||||
|
||||
callManager.readCallLink(
|
||||
SignalStore.internalValues().groupCallingServer(),
|
||||
requestCallLinkAuthCredentialPresentation(credentials.linkKeyBytes).serialize(),
|
||||
|
|
|
@ -15,4 +15,8 @@ message CallSyncEventJobRecord {
|
|||
|
||||
message CallSyncEventJobData {
|
||||
repeated CallSyncEventJobRecord records = 1;
|
||||
}
|
||||
|
||||
message CallLinkRefreshSinceTimestampJobData {
|
||||
uint64 timestamp = 1;
|
||||
}
|
|
@ -626,6 +626,15 @@ message SyncMessage {
|
|||
optional bytes adminPassKey = 2;
|
||||
}
|
||||
|
||||
message CallLogEvent {
|
||||
enum Type {
|
||||
CLEAR = 0;
|
||||
}
|
||||
|
||||
optional Type type = 1;
|
||||
optional uint64 timestamp = 2;
|
||||
}
|
||||
|
||||
optional Sent sent = 1;
|
||||
optional Contacts contacts = 2;
|
||||
reserved /*groups*/ 3;
|
||||
|
@ -646,6 +655,7 @@ message SyncMessage {
|
|||
optional PniChangeNumber pniChangeNumber = 18;
|
||||
optional CallEvent callEvent = 19;
|
||||
optional CallLinkUpdate callLinkUpdate = 20;
|
||||
optional CallLogEvent callLogEvent = 21;
|
||||
}
|
||||
|
||||
message AttachmentPointer {
|
||||
|
|
Loading…
Add table
Reference in a new issue