Skip to content

Commit

Permalink
Add call disposition syncing.
Browse files Browse the repository at this point in the history
  • Loading branch information
cody-signal authored and greyson-signal committed Dec 30, 2022
1 parent d471647 commit 06b414f
Show file tree
Hide file tree
Showing 23 changed files with 789 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.conversation.ConversationData.MessageRequestData;
import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationMessageFactory;
import org.thoughtcrime.securesms.database.CallTable;
import org.thoughtcrime.securesms.database.MmsSmsTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord;
Expand All @@ -32,6 +33,7 @@

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
Expand Down Expand Up @@ -98,6 +100,7 @@ private int getSizeInternal() {
AttachmentHelper attachmentHelper = new AttachmentHelper();
ReactionHelper reactionHelper = new ReactionHelper();
PaymentHelper paymentHelper = new PaymentHelper();
CallHelper callHelper = new CallHelper();
Set<ServiceId> referencedIds = new HashSet<>();

try (MmsSmsTable.Reader reader = MmsSmsTable.readerFor(db.getConversation(threadId, start, length))) {
Expand All @@ -109,6 +112,7 @@ private int getSizeInternal() {
reactionHelper.add(record);
attachmentHelper.add(record);
paymentHelper.add(record);
callHelper.add(record);

UpdateDescription description = record.getUpdateDisplayBody(context, null);
if (description != null) {
Expand Down Expand Up @@ -151,6 +155,12 @@ private int getSizeInternal() {
records = paymentHelper.buildUpdatedModels(records);
stopwatch.split("payment-models");

callHelper.fetchCalls();
stopwatch.split("calls");

records = callHelper.buildUpdatedModels(records);
stopwatch.split("call-models");

for (ServiceId serviceId : referencedIds) {
Recipient.resolved(RecipientId.from(serviceId));
}
Expand Down Expand Up @@ -202,6 +212,15 @@ record = SignalDatabase.payments().updateMessageWithPayment(record);
}
stopwatch.split("payments");

if (record.isCallLog() && !record.isGroupCall()) {
CallTable.Call call = SignalDatabase.calls().getCallByMessageId(record.getId());
if (call != null && record instanceof MediaMmsMessageRecord) {
record = ((MediaMmsMessageRecord) record).withCall(call);
}
}

stopwatch.split("calls");

return ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(ApplicationDependencies.getApplication(), record, mentions, isQuoted);
} else {
return null;
Expand Down Expand Up @@ -365,4 +384,35 @@ public void fetchPayments() {
.collect(Collectors.toList());
}
}

private static class CallHelper {
private final Collection<Long> messageIds = new LinkedList<>();
private Map<Long, CallTable.Call> messageIdToCall = Collections.emptyMap();

public void add(MessageRecord messageRecord) {
if (messageRecord.isCallLog() && !messageRecord.isGroupCall()) {
messageIds.add(messageRecord.getId());
}
}

public void fetchCalls() {
if (!messageIds.isEmpty()) {
messageIdToCall = SignalDatabase.calls().getCalls(messageIds);
}
}

@NonNull List<MessageRecord> buildUpdatedModels(@NonNull List<MessageRecord> records) {
return records.stream()
.map(record -> {
if (record.isCallLog() && record instanceof MediaMmsMessageRecord) {
CallTable.Call call = messageIdToCall.get(record.getId());
if (call != null) {
return ((MediaMmsMessageRecord) record).withCall(call);
}
}
return record;
})
.collect(Collectors.toList());
}
}
}
266 changes: 266 additions & 0 deletions app/src/main/java/org/thoughtcrime/securesms/database/CallTable.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
package org.thoughtcrime.securesms.database

import android.content.Context
import android.database.Cursor
import androidx.core.content.contentValuesOf
import org.signal.core.util.IntSerializer
import org.signal.core.util.Serializer
import org.signal.core.util.SqlUtil
import org.signal.core.util.logging.Log
import org.signal.core.util.readToList
import org.signal.core.util.readToSingleObject
import org.signal.core.util.requireLong
import org.signal.core.util.requireObject
import org.signal.core.util.select
import org.signal.core.util.update
import org.signal.core.util.withinTransaction
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.CallEvent

/**
* Contains details for each 1:1 call.
*/
class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper), RecipientIdDatabaseReference {

companion object {
private val TAG = Log.tag(CallTable::class.java)

private const val TABLE_NAME = "call"
private const val ID = "_id"
private const val CALL_ID = "call_id"
private const val MESSAGE_ID = "message_id"
private const val PEER = "peer"
private const val TYPE = "type"
private const val DIRECTION = "direction"
private const val EVENT = "event"

val CREATE_TABLE = """
CREATE TABLE $TABLE_NAME (
$ID INTEGER PRIMARY KEY,
$CALL_ID INTEGER NOT NULL UNIQUE,
$MESSAGE_ID INTEGER NOT NULL REFERENCES ${MessageTable.TABLE_NAME} (${MessageTable.ID}) ON DELETE CASCADE,
$PEER INTEGER NOT NULL REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE,
$TYPE INTEGER NOT NULL,
$DIRECTION INTEGER NOT NULL,
$EVENT INTEGER NOT NULL
)
""".trimIndent()

val CREATE_INDEXES = arrayOf(
"CREATE INDEX call_call_id_index ON $TABLE_NAME ($CALL_ID)",
"CREATE INDEX call_message_id_index ON $TABLE_NAME ($MESSAGE_ID)"
)
}

fun insertCall(callId: Long, timestamp: Long, peer: RecipientId, type: Type, direction: Direction, event: Event) {
val messageType: Long = Call.getMessageType(type, direction, event)

writableDatabase.withinTransaction {
val result = SignalDatabase.messages.insertCallLog(peer, messageType, timestamp)

val values = contentValuesOf(
CALL_ID to callId,
MESSAGE_ID to result.messageId,
PEER to peer.serialize(),
TYPE to Type.serialize(type),
DIRECTION to Direction.serialize(direction),
EVENT to Event.serialize(event)
)

writableDatabase.insert(TABLE_NAME, null, values)
}

ApplicationDependencies.getMessageNotifier().updateNotification(context)

Log.i(TAG, "Inserted call: $callId type: $type direction: $direction event:$event")
}

fun updateCall(callId: Long, event: Event): Call? {
return writableDatabase.withinTransaction {
writableDatabase
.update(TABLE_NAME)
.values(EVENT to Event.serialize(event))
.where("$CALL_ID = ?", callId)
.run()

val call = readableDatabase
.select()
.from(TABLE_NAME)
.where("$CALL_ID = ?", callId)
.run()
.readToSingleObject(Call.Deserializer)

if (call != null) {
Log.i(TAG, "Updated call: $callId event: $event")

SignalDatabase.messages.updateCallLog(call.messageId, call.messageType)
ApplicationDependencies.getMessageNotifier().updateNotification(context)
}

call
}
}

fun getCallById(callId: Long): Call? {
return readableDatabase
.select()
.from(TABLE_NAME)
.where("$CALL_ID = ?", callId)
.run()
.readToSingleObject(Call.Deserializer)
}

fun getCallByMessageId(messageId: Long): Call? {
return readableDatabase
.select()
.from(TABLE_NAME)
.where("$MESSAGE_ID = ?", messageId)
.run()
.readToSingleObject(Call.Deserializer)
}

fun getCalls(messageIds: Collection<Long>): Map<Long, Call> {
val calls = mutableMapOf<Long, Call>()
val queries = SqlUtil.buildCollectionQuery(MESSAGE_ID, messageIds)

queries.forEach { query ->
val cursor = readableDatabase
.select()
.from(TABLE_NAME)
.where(query.where, query.whereArgs)
.run()

calls.putAll(cursor.readToList { c -> c.requireLong(MESSAGE_ID) to Call.deserialize(c) })
}
return calls
}

override fun remapRecipient(fromId: RecipientId, toId: RecipientId) {
writableDatabase
.update(TABLE_NAME)
.values(PEER to toId.serialize())
.where("$PEER = ?", fromId)
.run()
}

data class Call(
val callId: Long,
val peer: RecipientId,
val type: Type,
val direction: Direction,
val event: Event,
val messageId: Long
) {
val messageType: Long = getMessageType(type, direction, event)

companion object Deserializer : Serializer<Call, Cursor> {
fun getMessageType(type: Type, direction: Direction, event: Event): Long {
return if (direction == Direction.INCOMING && event == Event.MISSED) {
if (type == Type.VIDEO_CALL) MmsSmsColumns.Types.MISSED_VIDEO_CALL_TYPE else MmsSmsColumns.Types.MISSED_AUDIO_CALL_TYPE
} else if (direction == Direction.INCOMING) {
if (type == Type.VIDEO_CALL) MmsSmsColumns.Types.INCOMING_VIDEO_CALL_TYPE else MmsSmsColumns.Types.INCOMING_AUDIO_CALL_TYPE
} else {
if (type == Type.VIDEO_CALL) MmsSmsColumns.Types.OUTGOING_VIDEO_CALL_TYPE else MmsSmsColumns.Types.OUTGOING_AUDIO_CALL_TYPE
}
}

override fun serialize(data: Call): Cursor {
throw UnsupportedOperationException()
}

override fun deserialize(data: Cursor): Call {
return Call(
callId = data.requireLong(CALL_ID),
peer = RecipientId.from(data.requireLong(PEER)),
type = data.requireObject(TYPE, Type.Serializer),
direction = data.requireObject(DIRECTION, Direction.Serializer),
event = data.requireObject(EVENT, Event.Serializer),
messageId = data.requireLong(MESSAGE_ID)
)
}
}
}

enum class Type(private val code: Int) {
AUDIO_CALL(0),
VIDEO_CALL(1);

companion object Serializer : IntSerializer<Type> {
override fun serialize(data: Type): Int = data.code

override fun deserialize(data: Int): Type {
return when (data) {
AUDIO_CALL.code -> AUDIO_CALL
VIDEO_CALL.code -> VIDEO_CALL
else -> throw IllegalArgumentException("Unknown type $data")
}
}

@JvmStatic
fun from(type: CallEvent.Type): Type? {
return when (type) {
CallEvent.Type.UNKNOWN_TYPE -> null
CallEvent.Type.AUDIO_CALL -> AUDIO_CALL
CallEvent.Type.VIDEO_CALL -> VIDEO_CALL
}
}
}
}

enum class Direction(private val code: Int) {
INCOMING(0),
OUTGOING(1);

companion object Serializer : IntSerializer<Direction> {
override fun serialize(data: Direction): Int = data.code

override fun deserialize(data: Int): Direction {
return when (data) {
INCOMING.code -> INCOMING
OUTGOING.code -> OUTGOING
else -> throw IllegalArgumentException("Unknown type $data")
}
}

@JvmStatic
fun from(direction: CallEvent.Direction): Direction? {
return when (direction) {
CallEvent.Direction.UNKNOWN_DIRECTION -> null
CallEvent.Direction.INCOMING -> INCOMING
CallEvent.Direction.OUTGOING -> OUTGOING
}
}
}
}

enum class Event(private val code: Int) {
ONGOING(0),
ACCEPTED(1),
NOT_ACCEPTED(2),
MISSED(3);

companion object Serializer : IntSerializer<Event> {
override fun serialize(data: Event): Int = data.code

override fun deserialize(data: Int): Event {
return when (data) {
ONGOING.code -> ONGOING
ACCEPTED.code -> ACCEPTED
NOT_ACCEPTED.code -> NOT_ACCEPTED
MISSED.code -> MISSED
else -> throw IllegalArgumentException("Unknown type $data")
}
}

@JvmStatic
fun from(event: CallEvent.Event): Event? {
return when (event) {
CallEvent.Event.UNKNOWN_ACTION -> null
CallEvent.Event.ACCEPTED -> ACCEPTED
CallEvent.Event.NOT_ACCEPTED -> NOT_ACCEPTED
}
}
}
}
}

0 comments on commit 06b414f

Please sign in to comment.