Add call disposition syncing.

This commit is contained in:
Cody Henthorne
2022-12-21 23:56:28 -05:00
committed by Greyson Parrelli
parent d471647e12
commit 06b414f4ef
23 changed files with 788 additions and 76 deletions

View File

@@ -13,6 +13,7 @@ import org.signal.paging.PagedDataSource;
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;
@@ -32,6 +33,7 @@ import org.whispersystems.signalservice.api.util.UuidUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
@@ -98,6 +100,7 @@ public class ConversationDataSource implements PagedDataSource<MessageId, Conver
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))) {
@@ -109,6 +112,7 @@ public class ConversationDataSource implements PagedDataSource<MessageId, Conver
reactionHelper.add(record);
attachmentHelper.add(record);
paymentHelper.add(record);
callHelper.add(record);
UpdateDescription description = record.getUpdateDisplayBody(context, null);
if (description != null) {
@@ -151,6 +155,12 @@ public class ConversationDataSource implements PagedDataSource<MessageId, Conver
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));
}
@@ -202,6 +212,15 @@ public class ConversationDataSource implements PagedDataSource<MessageId, Conver
}
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;
@@ -365,4 +384,35 @@ public class ConversationDataSource implements PagedDataSource<MessageId, Conver
.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());
}
}
}

View File

@@ -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
}
}
}
}
}

View File

@@ -559,23 +559,12 @@ public class MessageTable extends DatabaseTable implements MmsSmsColumns, Recipi
return results;
}
public @NonNull Pair<Long, Long> insertReceivedCall(@NonNull RecipientId address, boolean isVideoOffer) {
return insertCallLog(address, isVideoOffer ? Types.INCOMING_VIDEO_CALL_TYPE : Types.INCOMING_AUDIO_CALL_TYPE, false, System.currentTimeMillis());
}
public @NonNull Pair<Long, Long> insertOutgoingCall(@NonNull RecipientId address, boolean isVideoOffer) {
return insertCallLog(address, isVideoOffer ? Types.OUTGOING_VIDEO_CALL_TYPE : Types.OUTGOING_AUDIO_CALL_TYPE, false, System.currentTimeMillis());
}
public @NonNull Pair<Long, Long> insertMissedCall(@NonNull RecipientId address, long timestamp, boolean isVideoOffer) {
return insertCallLog(address, isVideoOffer ? Types.MISSED_VIDEO_CALL_TYPE : Types.MISSED_AUDIO_CALL_TYPE, true, timestamp);
}
private @NonNull Pair<Long, Long> insertCallLog(@NonNull RecipientId recipientId, long type, boolean unread, long timestamp) {
public @NonNull InsertResult insertCallLog(@NonNull RecipientId recipientId, long type, long timestamp) {
boolean unread = Types.isMissedAudioCall(type) || Types.isMissedVideoCall(type);
Recipient recipient = Recipient.resolved(recipientId);
long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(recipient);
ContentValues values = new ContentValues(6);
ContentValues values = new ContentValues(7);
values.put(RECIPIENT_ID, recipientId.serialize());
values.put(RECIPIENT_DEVICE_ID, 1);
values.put(DATE_RECEIVED, System.currentTimeMillis());
@@ -584,19 +573,40 @@ public class MessageTable extends DatabaseTable implements MmsSmsColumns, Recipi
values.put(TYPE, type);
values.put(THREAD_ID, threadId);
SQLiteDatabase db = databaseHelper.getSignalWritableDatabase();
long messageId = db.insert(TABLE_NAME, null, values);
long messageId = getWritableDatabase().insert(TABLE_NAME, null, values);
boolean keepThreadArchived = SignalStore.settings().shouldKeepMutedChatsArchived() && Recipient.resolved(recipientId).isMuted();
if (unread) {
SignalDatabase.threads().incrementUnread(threadId, 1, 0);
}
boolean keepThreadArchived = SignalStore.settings().shouldKeepMutedChatsArchived() && Recipient.resolved(recipientId).isMuted();
SignalDatabase.threads().update(threadId, !keepThreadArchived);
notifyConversationListeners(threadId);
TrimThreadJob.enqueueAsync(threadId);
return new Pair<>(messageId, threadId);
return new InsertResult(messageId, threadId);
}
public void updateCallLog(long messageId, long type) {
boolean unread = Types.isMissedAudioCall(type) || Types.isMissedVideoCall(type);
ContentValues values = new ContentValues(2);
values.put(TYPE, type);
values.put(READ, unread ? 0 : 1);
getWritableDatabase().update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(messageId));
long threadId = getThreadIdForMessage(messageId);
Recipient recipient = SignalDatabase.threads().getRecipientForThreadId(threadId);
boolean keepThreadArchived = SignalStore.settings().shouldKeepMutedChatsArchived() && recipient != null && recipient.isMuted();
if (unread) {
SignalDatabase.threads().incrementUnread(threadId, 1, 0);
}
SignalDatabase.threads().update(threadId, !keepThreadArchived);
notifyConversationListeners(threadId);
ApplicationDependencies.getDatabaseObserver().notifyMessageUpdateObservers(new MessageId(messageId));
}
public void insertOrUpdateGroupCall(@NonNull RecipientId groupRecipientId,
@@ -4179,6 +4189,7 @@ public class MessageTable extends DatabaseTable implements MmsSmsColumns, Recipi
message.getStoryType(),
message.getParentStoryId(),
message.getGiftBadge(),
null,
null);
}
}
@@ -4367,7 +4378,7 @@ public class MessageTable extends DatabaseTable implements MmsSmsColumns, Recipi
networkFailures, subscriptionId, expiresIn, expireStarted,
isViewOnce, readReceiptCount, quote, contacts, previews, unidentified, Collections.emptyList(),
remoteDelete, mentionsSelf, notifiedTimestamp, viewedReceiptCount, receiptTimestamp, messageRanges,
storyType, parentStoryId, giftBadge, null);
storyType, parentStoryId, giftBadge, null, null);
}
private Set<IdentityKeyMismatch> getMismatchedIdentities(String document) {

View File

@@ -74,6 +74,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
val cdsTable: CdsTable = CdsTable(context, this)
val remoteMegaphoneTable: RemoteMegaphoneTable = RemoteMegaphoneTable(context, this)
val pendingPniSignatureMessageTable: PendingPniSignatureMessageTable = PendingPniSignatureMessageTable(context, this)
val callTable: CallTable = CallTable(context, this)
override fun onOpen(db: net.zetetic.database.sqlcipher.SQLiteDatabase) {
db.setForeignKeyConstraintsEnabled(true)
@@ -109,6 +110,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
db.execSQL(CdsTable.CREATE_TABLE)
db.execSQL(RemoteMegaphoneTable.CREATE_TABLE)
db.execSQL(PendingPniSignatureMessageTable.CREATE_TABLE)
db.execSQL(CallTable.CREATE_TABLE)
executeStatements(db, SearchTable.CREATE_TABLE)
executeStatements(db, RemappedRecordTables.CREATE_TABLE)
executeStatements(db, MessageSendLogTables.CREATE_TABLE)
@@ -133,6 +135,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
executeStatements(db, StorySendTable.CREATE_INDEXS)
executeStatements(db, DistributionListTables.CREATE_INDEXES)
executeStatements(db, PendingPniSignatureMessageTable.CREATE_INDEXES)
executeStatements(db, CallTable.CREATE_INDEXES)
executeStatements(db, SearchTable.CREATE_TRIGGERS)
executeStatements(db, MessageSendLogTables.CREATE_TRIGGERS)
@@ -523,5 +526,10 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
@get:JvmName("remoteMegaphones")
val remoteMegaphones: RemoteMegaphoneTable
get() = instance!!.remoteMegaphoneTable
@get:JvmStatic
@get:JvmName("calls")
val calls: CallTable
get() = instance!!.callTable
}
}

View File

@@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V166_ThreadAndMessa
import org.thoughtcrime.securesms.database.helpers.migration.V167_RecreateReactionTriggers
import org.thoughtcrime.securesms.database.helpers.migration.V168_SingleMessageTableMigration
import org.thoughtcrime.securesms.database.helpers.migration.V169_EmojiSearchIndexRank
import org.thoughtcrime.securesms.database.helpers.migration.V170_CallTableMigration
/**
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
@@ -33,7 +34,7 @@ object SignalDatabaseMigrations {
val TAG: String = Log.tag(SignalDatabaseMigrations.javaClass)
const val DATABASE_VERSION = 169
const val DATABASE_VERSION = 170
@JvmStatic
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
@@ -120,6 +121,10 @@ object SignalDatabaseMigrations {
if (oldVersion < 169) {
V169_EmojiSearchIndexRank.migrate(context, db, oldVersion, newVersion)
}
if (oldVersion < 170) {
V170_CallTableMigration.migrate(context, db, oldVersion, newVersion)
}
}
@JvmStatic

View File

@@ -0,0 +1,26 @@
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import net.zetetic.database.sqlcipher.SQLiteDatabase
@Suppress("ClassName")
object V170_CallTableMigration : SignalDatabaseMigration {
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL(
"""
CREATE TABLE call (
_id INTEGER PRIMARY KEY,
call_id INTEGER NOT NULL UNIQUE,
message_id INTEGER NOT NULL REFERENCES mms (_id) ON DELETE CASCADE,
peer INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
type INTEGER NOT NULL,
direction INTEGER NOT NULL,
event INTEGER NOT NULL
)
""".trimIndent()
)
db.execSQL("CREATE INDEX call_call_id_index ON call (call_id)")
db.execSQL("CREATE INDEX call_message_id_index ON call (message_id)")
}
}

View File

@@ -22,6 +22,7 @@ import android.text.SpannableString;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.core.content.ContextCompat;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
@@ -29,6 +30,7 @@ import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.database.CallTable;
import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.database.MmsSmsColumns.Status;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
@@ -39,6 +41,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.payments.Payment;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.signalservice.api.payments.FormatterOptions;
import java.util.HashMap;
@@ -47,6 +50,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
@@ -60,9 +64,10 @@ import java.util.stream.Collectors;
public class MediaMmsMessageRecord extends MmsMessageRecord {
private final static String TAG = Log.tag(MediaMmsMessageRecord.class);
private final boolean mentionsSelf;
private final BodyRangeList messageRanges;
private final Payment payment;
private final boolean mentionsSelf;
private final BodyRangeList messageRanges;
private final Payment payment;
private final CallTable.Call call;
public MediaMmsMessageRecord(long id,
Recipient conversationRecipient,
@@ -97,7 +102,8 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
@NonNull StoryType storyType,
@Nullable ParentStoryId parentStoryId,
@Nullable GiftBadge giftBadge,
@Nullable Payment payment)
@Nullable Payment payment,
@Nullable CallTable.Call call)
{
super(id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent,
dateReceived, dateServer, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures,
@@ -107,6 +113,7 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
this.mentionsSelf = mentionsSelf;
this.messageRanges = messageRanges;
this.payment = payment;
this.call = call;
}
@Override
@@ -137,6 +144,40 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
return super.getDisplayBody(context);
}
@Override
public @Nullable UpdateDescription getUpdateDisplayBody(@NonNull Context context, @Nullable Consumer<RecipientId> recipientClickHandler) {
if (isCallLog() && call != null) {
boolean accepted = call.getEvent() == CallTable.Event.ACCEPTED;
String callDateString = getCallDateString(context);
if (call.getDirection() == CallTable.Direction.OUTGOING) {
if (call.getType() == CallTable.Type.AUDIO_CALL) {
int updateString = accepted ? R.string.MessageRecord_you_called_date : R.string.MessageRecord_unanswered_audio_call_date;
return staticUpdateDescription(context.getString(updateString, callDateString), R.drawable.ic_update_audio_call_outgoing_16);
} else {
int updateString = accepted ? R.string.MessageRecord_you_called_date : R.string.MessageRecord_unanswered_video_call_date;
return staticUpdateDescription(context.getString(updateString, callDateString), R.drawable.ic_update_video_call_outgoing_16);
}
} else {
boolean isVideoCall = call.getType() == CallTable.Type.VIDEO_CALL;
boolean isMissed = call.getEvent() == CallTable.Event.MISSED;
if (accepted) {
int icon = isVideoCall ? R.drawable.ic_update_video_call_incoming_16 : R.drawable.ic_update_audio_call_incoming_16;
return fromRecipient(getIndividualRecipient(), r -> context.getString(R.string.MessageRecord_s_called_you_date, r.getDisplayName(context), callDateString), icon);
} else if (isMissed) {
return isVideoCall ? staticUpdateDescription(context.getString(R.string.MessageRecord_missed_video_call_date, callDateString), R.drawable.ic_update_video_call_missed_16, ContextCompat.getColor(context, R.color.core_red_shade), ContextCompat.getColor(context, R.color.core_red))
: staticUpdateDescription(context.getString(R.string.MessageRecord_missed_audio_call_date, callDateString), R.drawable.ic_update_audio_call_missed_16, ContextCompat.getColor(context, R.color.core_red_shade), ContextCompat.getColor(context, R.color.core_red));
} else {
return isVideoCall ? fromRecipient(getIndividualRecipient(), r -> context.getString(R.string.MessageRecord_declined_video_call_date, callDateString), R.drawable.ic_update_video_call_incoming_16)
: fromRecipient(getIndividualRecipient(), r -> context.getString(R.string.MessageRecord_declined_audio_call_date, callDateString), R.drawable.ic_update_audio_call_incoming_16);
}
}
}
return super.getUpdateDisplayBody(context, recipientClickHandler);
}
public @Nullable BodyRangeList getMessageRanges() {
return messageRanges;
}
@@ -155,18 +196,22 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
return payment;
}
public @Nullable CallTable.Call getCall() {
return call;
}
public @NonNull MediaMmsMessageRecord withReactions(@NonNull List<ReactionRecord> reactions) {
return new MediaMmsMessageRecord(getId(), getRecipient(), getIndividualRecipient(), getRecipientDeviceId(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), reactions, isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment());
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall());
}
public @NonNull MediaMmsMessageRecord withoutQuote() {
return new MediaMmsMessageRecord(getId(), getRecipient(), getIndividualRecipient(), getRecipientDeviceId(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), null, getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment());
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall());
}
public @NonNull MediaMmsMessageRecord withAttachments(@NonNull Context context, @NonNull List<DatabaseAttachment> attachments) {
@@ -187,14 +232,22 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
return new MediaMmsMessageRecord(getId(), getRecipient(), getIndividualRecipient(), getRecipientDeviceId(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), slideDeck,
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), quote, contacts, linkPreviews, isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment());
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall());
}
public @NonNull MediaMmsMessageRecord withPayment(@NonNull Payment payment) {
return new MediaMmsMessageRecord(getId(), getRecipient(), getIndividualRecipient(), getRecipientDeviceId(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), payment);
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), payment, getCall());
}
public @NonNull MediaMmsMessageRecord withCall(@Nullable CallTable.Call call) {
return new MediaMmsMessageRecord(getId(), getRecipient(), getIndividualRecipient(), getRecipientDeviceId(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), call);
}
private static @NonNull List<Contact> updateContacts(@NonNull List<Contact> contacts, @NonNull Map<AttachmentId, DatabaseAttachment> attachmentIdMap) {

View File

@@ -337,29 +337,29 @@ public abstract class MessageRecord extends DisplayRecord {
return null;
}
private @NonNull String getCallDateString(@NonNull Context context) {
protected @NonNull String getCallDateString(@NonNull Context context) {
return DateUtils.getSimpleRelativeTimeSpanString(context, Locale.getDefault(), getDateSent());
}
private static @NonNull UpdateDescription fromRecipient(@NonNull Recipient recipient,
@NonNull Function<Recipient, String> stringGenerator,
@DrawableRes int iconResource)
protected static @NonNull UpdateDescription fromRecipient(@NonNull Recipient recipient,
@NonNull Function<Recipient, String> stringGenerator,
@DrawableRes int iconResource)
{
return UpdateDescription.mentioning(Collections.singletonList(recipient.getServiceId().orElse(ServiceId.UNKNOWN)),
() -> new SpannableString(stringGenerator.apply(recipient.resolve())),
iconResource);
}
private static @NonNull UpdateDescription staticUpdateDescription(@NonNull String string,
@DrawableRes int iconResource)
protected static @NonNull UpdateDescription staticUpdateDescription(@NonNull String string,
@DrawableRes int iconResource)
{
return UpdateDescription.staticDescription(string, iconResource);
}
private static @NonNull UpdateDescription staticUpdateDescription(@NonNull String string,
@DrawableRes int iconResource,
@ColorInt int lightTint,
@ColorInt int darkTint)
protected static @NonNull UpdateDescription staticUpdateDescription(@NonNull String string,
@DrawableRes int iconResource,
@ColorInt int lightTint,
@ColorInt int darkTint)
{
return UpdateDescription.staticDescription(string, iconResource, lightTint, darkTint);
}

View File

@@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.contactshare.ContactModelMapper;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.crypto.SecurityEvent;
import org.thoughtcrime.securesms.database.AttachmentTable;
import org.thoughtcrime.securesms.database.CallTable;
import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.GroupTable.GroupRecord;
import org.thoughtcrime.securesms.database.GroupReceiptTable;
@@ -169,6 +170,7 @@ import org.whispersystems.signalservice.api.payments.Money;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage;
import java.io.IOException;
import java.security.SecureRandom;
@@ -350,6 +352,7 @@ public final class MessageContentProcessor {
else if (syncMessage.getOutgoingPaymentMessage().isPresent()) handleSynchronizeOutgoingPayment(content, syncMessage.getOutgoingPaymentMessage().get());
else if (syncMessage.getKeys().isPresent()) handleSynchronizeKeys(syncMessage.getKeys().get(), content.getTimestamp());
else if (syncMessage.getContacts().isPresent()) handleSynchronizeContacts(syncMessage.getContacts().get(), content.getTimestamp());
else if (syncMessage.getCallEvent().isPresent()) handleSynchronizeCallEvent(syncMessage.getCallEvent().get(), content.getTimestamp());
else warn(String.valueOf(content.getTimestamp()), "Contains no known sync types...");
} else if (content.getCallMessage().isPresent()) {
log(String.valueOf(content.getTimestamp()), "Got call message...");
@@ -627,7 +630,7 @@ public final class MessageContentProcessor {
@NonNull AnswerMessage message,
@NonNull Recipient senderRecipient)
{
log(String.valueOf(content), "handleCallAnswerMessage...");
log(content.getTimestamp(), "handleCallAnswerMessage...");
RemotePeer remotePeer = new RemotePeer(senderRecipient.getId(), new CallId(message.getId()));
byte[] remoteIdentityKey = ApplicationDependencies.getProtocolStore().aci().identities().getIdentityRecord(senderRecipient.getId()).map(record -> record.getIdentityKey().serialize()).get();
@@ -641,7 +644,7 @@ public final class MessageContentProcessor {
@NonNull List<IceUpdateMessage> messages,
@NonNull Recipient senderRecipient)
{
log(String.valueOf(content), "handleCallIceUpdateMessage... " + messages.size());
log(content.getTimestamp(), "handleCallIceUpdateMessage... " + messages.size());
List<byte[]> iceCandidates = new ArrayList<>(messages.size());
long callId = -1;
@@ -663,7 +666,7 @@ public final class MessageContentProcessor {
@NonNull Optional<Long> smsMessageId,
@NonNull Recipient senderRecipient)
{
log(String.valueOf(content), "handleCallHangupMessage");
log(content.getTimestamp(), "handleCallHangupMessage");
if (smsMessageId.isPresent()) {
SignalDatabase.messages().markAsMissedCall(smsMessageId.get(), false);
} else {
@@ -1228,6 +1231,45 @@ public final class MessageContentProcessor {
ApplicationDependencies.getJobManager().add(new MultiDeviceContactSyncJob(contactsAttachment));
}
private void handleSynchronizeCallEvent(@NonNull SyncMessage.CallEvent callEvent, long envelopeTimestamp) {
if (!callEvent.hasId()) {
log(envelopeTimestamp, "Synchronize call event missing call id, ignoring.");
return;
}
long callId = callEvent.getId();
long timestamp = callEvent.getTimestamp();
CallTable.Type type = CallTable.Type.from(callEvent.getType());
CallTable.Direction direction = CallTable.Direction.from(callEvent.getDirection());
CallTable.Event event = CallTable.Event.from(callEvent.getEvent());
if (timestamp == 0 || type == null || direction == null || event == null || !callEvent.hasPeerUuid()) {
warn(envelopeTimestamp, "Call event sync message is not valid, ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasPeerUuid());
return;
}
ServiceId serviceId = ServiceId.fromByteString(callEvent.getPeerUuid());
RecipientId recipientId = RecipientId.from(serviceId);
log(envelopeTimestamp, "Synchronize call event call: " + callId);
CallTable.Call call = SignalDatabase.calls().getCallById(callId);
if (call != null) {
boolean typeMismatch = call.getType() != type;
boolean directionMismatch = call.getDirection() != direction;
boolean eventDowngrade = call.getEvent() == CallTable.Event.ACCEPTED && event != CallTable.Event.ACCEPTED;
boolean peerMismatch = !call.getPeer().equals(recipientId);
if (typeMismatch || directionMismatch || eventDowngrade || peerMismatch) {
warn(envelopeTimestamp, "Call event sync message is not valid for existing call record, ignoring. type: " + type + " direction: " + direction + " event: " + event + " peerMismatch: " + peerMismatch);
} else {
SignalDatabase.calls().updateCall(callId, event);
}
} else {
SignalDatabase.calls().insertCall(callId, timestamp, recipientId, type, direction, event);
}
}
private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content,
@NonNull SentTranscriptMessage message,
@NonNull Recipient senderRecipient)

View File

@@ -0,0 +1,36 @@
package org.thoughtcrime.securesms.service.webrtc
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.ringrtc.RemotePeer
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.CallEvent
/**
* Helper for creating call event sync messages.
*/
object CallEventSyncMessageUtil {
@JvmStatic
fun createAcceptedSyncMessage(remotePeer: RemotePeer, timestamp: Long, isOutgoing: Boolean, isVideoCall: Boolean): CallEvent {
return CallEvent
.newBuilder()
.setPeerUuid(Recipient.resolved(remotePeer.id).requireServiceId().toByteString())
.setId(remotePeer.callId.longValue())
.setTimestamp(timestamp)
.setType(if (isVideoCall) CallEvent.Type.VIDEO_CALL else CallEvent.Type.AUDIO_CALL)
.setDirection(if (isOutgoing) CallEvent.Direction.OUTGOING else CallEvent.Direction.INCOMING)
.setEvent(CallEvent.Event.ACCEPTED)
.build()
}
@JvmStatic
fun createNotAcceptedSyncMessage(remotePeer: RemotePeer, timestamp: Long, isOutgoing: Boolean, isVideoCall: Boolean): CallEvent {
return CallEvent
.newBuilder()
.setPeerUuid(Recipient.resolved(remotePeer.id).requireServiceId().toByteString())
.setId(remotePeer.callId.longValue())
.setTimestamp(timestamp)
.setType(if (isVideoCall) CallEvent.Type.VIDEO_CALL else CallEvent.Type.AUDIO_CALL)
.setDirection(if (isOutgoing) CallEvent.Direction.OUTGOING else CallEvent.Direction.INCOMING)
.setEvent(CallEvent.Event.NOT_ACCEPTED)
.build()
}
}

View File

@@ -37,6 +37,12 @@ public class CallSetupActionProcessorDelegate extends WebRtcActionProcessor {
RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer();
webRtcInteractor.sendAcceptedCallEventSyncMessage(
activePeer,
currentState.getCallInfoState().getCallState() == WebRtcViewModel.State.CALL_RINGING,
currentState.getCallSetupState(activePeer).isAcceptWithVideo() || currentState.getLocalDeviceState().getCameraState().isEnabled()
);
ApplicationDependencies.getAppForegroundObserver().removeListener(webRtcInteractor.getForegroundListener());
webRtcInteractor.startAudioCommunication();
webRtcInteractor.activateCall(activePeer.getId());

View File

@@ -10,6 +10,7 @@ import org.signal.core.util.logging.Log;
import org.signal.ringrtc.CallException;
import org.signal.ringrtc.CallId;
import org.signal.ringrtc.CallManager;
import org.thoughtcrime.securesms.database.CallTable;
import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
@@ -130,8 +131,6 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor {
Log.i(TAG, "handleAcceptCall(): call_id: " + activePeer.getCallId());
SignalDatabase.messages().insertReceivedCall(activePeer.getId(), currentState.getCallSetupState(activePeer).isRemoteVideoOffer());
currentState = currentState.builder()
.changeCallSetupState(activePeer.getCallId())
.acceptWithVideo(answerWithVideo)
@@ -156,10 +155,13 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor {
Log.i(TAG, "handleDenyCall():");
webRtcInteractor.sendNotAcceptedCallEventSyncMessage(activePeer,
false,
currentState.getCallSetupState(activePeer).isAcceptWithVideo() || currentState.getLocalDeviceState().getCameraState().isEnabled());
try {
webRtcInteractor.rejectIncomingCall(activePeer.getId());
webRtcInteractor.getCallManager().hangup();
SignalDatabase.messages().insertMissedCall(activePeer.getId(), System.currentTimeMillis(), currentState.getCallSetupState(activePeer).isRemoteVideoOffer());
return terminate(currentState, activePeer);
} catch (CallException e) {
return callFailure(currentState, "hangup() failed: ", e);
@@ -173,6 +175,14 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor {
Recipient recipient = remotePeer.getRecipient();
activePeer.localRinging();
SignalDatabase.calls().insertCall(remotePeer.getCallId().longValue(),
System.currentTimeMillis(),
remotePeer.getId(),
currentState.getCallSetupState(activePeer).isRemoteVideoOffer() ? CallTable.Type.VIDEO_CALL : CallTable.Type.AUDIO_CALL,
CallTable.Direction.INCOMING,
CallTable.Event.ONGOING);
webRtcInteractor.updatePhoneState(LockManager.PhoneState.INTERACTIVE);
boolean shouldDisturbUserWithCall = DoNotDisturbUtil.shouldDisturbUserWithCall(context.getApplicationContext(), recipient);

View File

@@ -11,6 +11,7 @@ import org.signal.ringrtc.CallException;
import org.signal.ringrtc.CallId;
import org.signal.ringrtc.CallManager;
import org.thoughtcrime.securesms.components.webrtc.EglBaseWrapper;
import org.thoughtcrime.securesms.database.CallTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.events.CallParticipant;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
@@ -83,7 +84,13 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor {
}
RecipientUtil.setAndSendUniversalExpireTimerIfNecessary(context, Recipient.resolved(remotePeer.getId()), SignalDatabase.threads().getThreadIdIfExistsFor(remotePeer.getId()));
SignalDatabase.messages().insertOutgoingCall(remotePeer.getId(), isVideoCall);
SignalDatabase.calls().insertCall(remotePeer.getCallId().longValue(),
System.currentTimeMillis(),
remotePeer.getId(),
isVideoCall ? CallTable.Type.VIDEO_CALL : CallTable.Type.AUDIO_CALL,
CallTable.Direction.OUTGOING,
CallTable.Event.ONGOING);
EglBaseWrapper.replaceHolder(EglBaseWrapper.OUTGOING_PLACEHOLDER, remotePeer.getCallId().longValue());
@@ -236,6 +243,13 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor {
@Override
protected @NonNull WebRtcServiceState handleLocalHangup(@NonNull WebRtcServiceState currentState) {
RemotePeer activePeer = currentState.getCallInfoState().getActivePeer();
if (activePeer != null) {
webRtcInteractor.sendNotAcceptedCallEventSyncMessage(activePeer,
true,
currentState.getCallSetupState(activePeer).isAcceptWithVideo() || currentState.getLocalDeviceState().getCameraState().isEnabled());
}
return activeCallDelegate.handleLocalHangup(currentState);
}
@@ -246,6 +260,19 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor {
@Override
protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull CallManager.CallEvent endedRemoteEvent, @NonNull RemotePeer remotePeer) {
RemotePeer activePeer = currentState.getCallInfoState().getActivePeer();
if (activePeer != null &&
(endedRemoteEvent == CallManager.CallEvent.ENDED_REMOTE_HANGUP ||
endedRemoteEvent == CallManager.CallEvent.ENDED_REMOTE_HANGUP_NEED_PERMISSION ||
endedRemoteEvent == CallManager.CallEvent.ENDED_REMOTE_BUSY ||
endedRemoteEvent == CallManager.CallEvent.ENDED_TIMEOUT ||
endedRemoteEvent == CallManager.CallEvent.ENDED_REMOTE_GLARE))
{
webRtcInteractor.sendNotAcceptedCallEventSyncMessage(activePeer,
true,
currentState.getCallSetupState(activePeer).isAcceptWithVideo() || currentState.getLocalDeviceState().getCameraState().isEnabled());
}
return activeCallDelegate.handleEndedRemote(currentState, endedRemoteEvent, remotePeer);
}

View File

@@ -31,6 +31,7 @@ import org.signal.storageservice.protos.groups.GroupExternalCredential;
import org.thoughtcrime.securesms.WebRtcCallActivity;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.CallTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.events.GroupCallPeekEvent;
@@ -66,8 +67,10 @@ import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
import org.whispersystems.signalservice.api.messages.calls.OpaqueMessage;
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
import org.whispersystems.signalservice.api.messages.calls.TurnServerInfo;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage;
import java.io.IOException;
import java.util.Collection;
@@ -521,7 +524,7 @@ private void processStateless(@NonNull Function1<WebRtcEphemeralState, WebRtcEph
Log.i(TAG, "Ignoring event: " + event);
break;
default:
throw new AssertionError("Unexpected event: " + event.toString());
throw new AssertionError("Unexpected event: " + event);
}
return s;
@@ -825,18 +828,28 @@ private void processStateless(@NonNull Function1<WebRtcEphemeralState, WebRtcEph
});
}
public void insertMissedCall(@NonNull RemotePeer remotePeer, boolean signal, long timestamp, boolean isVideoOffer) {
Pair<Long, Long> messageAndThreadId = SignalDatabase.messages().insertMissedCall(remotePeer.getId(), timestamp, isVideoOffer);
public void insertMissedCall(@NonNull RemotePeer remotePeer, long timestamp, boolean isVideoOffer) {
CallTable.Call call = SignalDatabase.calls()
.updateCall(remotePeer.getCallId().longValue(), CallTable.Event.MISSED);
ApplicationDependencies.getMessageNotifier()
.updateNotification(context, ConversationId.forConversation(messageAndThreadId.second()), signal);
if (call == null) {
CallTable.Type type = isVideoOffer ? CallTable.Type.VIDEO_CALL : CallTable.Type.AUDIO_CALL;
SignalDatabase.calls()
.insertCall(remotePeer.getCallId().longValue(), timestamp, remotePeer.getId(), type, CallTable.Direction.INCOMING, CallTable.Event.MISSED);
}
}
public void insertReceivedCall(@NonNull RemotePeer remotePeer, boolean signal, boolean isVideoOffer) {
Pair<Long, Long> messageAndThreadId = SignalDatabase.messages().insertReceivedCall(remotePeer.getId(), isVideoOffer);
public void insertReceivedCall(@NonNull RemotePeer remotePeer, boolean isVideoOffer) {
CallTable.Call call = SignalDatabase.calls()
.updateCall(remotePeer.getCallId().longValue(), CallTable.Event.ACCEPTED);
ApplicationDependencies.getMessageNotifier()
.updateNotification(context, ConversationId.forConversation(messageAndThreadId.second()), signal);
if (call == null) {
CallTable.Type type = isVideoOffer ? CallTable.Type.VIDEO_CALL : CallTable.Type.AUDIO_CALL;
SignalDatabase.calls()
.insertCall(remotePeer.getCallId().longValue(), System.currentTimeMillis(), remotePeer.getId(), type, CallTable.Direction.INCOMING, CallTable.Event.ACCEPTED);
}
}
public void retrieveTurnServers(@NonNull RemotePeer remotePeer) {
@@ -919,6 +932,40 @@ private void processStateless(@NonNull Function1<WebRtcEphemeralState, WebRtcEph
});
}
public void sendAcceptedCallEventSyncMessage(@NonNull RemotePeer remotePeer, boolean isOutgoing, boolean isVideoCall) {
SignalDatabase
.calls()
.updateCall(remotePeer.getCallId().longValue(), CallTable.Event.ACCEPTED);
if (TextSecurePreferences.isMultiDevice(context)) {
networkExecutor.execute(() -> {
try {
SyncMessage.CallEvent callEvent = CallEventSyncMessageUtil.createAcceptedSyncMessage(remotePeer, System.currentTimeMillis(), isOutgoing, isVideoCall);
ApplicationDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent), Optional.empty());
} catch (IOException | UntrustedIdentityException e) {
Log.w(TAG, "Unable to send call event sync message for " + remotePeer.getCallId().longValue(), e);
}
});
}
}
public void sendNotAcceptedCallEventSyncMessage(@NonNull RemotePeer remotePeer, boolean isOutgoing, boolean isVideoCall) {
SignalDatabase
.calls()
.updateCall(remotePeer.getCallId().longValue(), CallTable.Event.NOT_ACCEPTED);
if (TextSecurePreferences.isMultiDevice(context)) {
networkExecutor.execute(() -> {
try {
SyncMessage.CallEvent callEvent = CallEventSyncMessageUtil.createNotAcceptedSyncMessage(remotePeer, System.currentTimeMillis(), isOutgoing, isVideoCall);
ApplicationDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent), Optional.empty());
} catch (IOException | UntrustedIdentityException e) {
Log.w(TAG, "Unable to send call event sync message for " + remotePeer.getCallId().longValue(), e);
}
});
}
}
private void processSendMessageFailureWithChangeDetection(@NonNull RemotePeer remotePeer,
@NonNull ProcessAction failureProcessAction)
{

View File

@@ -109,11 +109,11 @@ public class WebRtcInteractor {
}
void insertMissedCall(@NonNull RemotePeer remotePeer, long timestamp, boolean isVideoOffer) {
signalCallManager.insertMissedCall(remotePeer, true, timestamp, isVideoOffer);
signalCallManager.insertMissedCall(remotePeer, timestamp, isVideoOffer);
}
void insertReceivedCall(@NonNull RemotePeer remotePeer, boolean isVideoOffer) {
signalCallManager.insertReceivedCall(remotePeer, true, isVideoOffer);
signalCallManager.insertReceivedCall(remotePeer, isVideoOffer);
}
boolean startWebRtcCallActivityIfPossible() {
@@ -187,4 +187,12 @@ public class WebRtcInteractor {
public void requestGroupMembershipProof(GroupId.V2 groupId, int groupCallHashCode) {
signalCallManager.requestGroupMembershipToken(groupId, groupCallHashCode);
}
public void sendAcceptedCallEventSyncMessage(@NonNull RemotePeer remotePeer, boolean isOutgoing, boolean isVideoCall) {
signalCallManager.sendAcceptedCallEventSyncMessage(remotePeer, isOutgoing, isVideoCall);
}
public void sendNotAcceptedCallEventSyncMessage(@NonNull RemotePeer remotePeer, boolean isOutgoing, boolean isVideoCall) {
signalCallManager.sendNotAcceptedCallEventSyncMessage(remotePeer, isOutgoing, isVideoCall);
}
}