From 6d3924ba43141db388d4fdbee5a683af9eb41b6e Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Mon, 20 Nov 2023 10:03:37 -0500 Subject: [PATCH] Add group call NOT_ACCEPTED sync handling. --- .../securesms/database/CallTable.kt | 62 +++++++++++++++++++ .../securesms/jobs/CallSyncEventJob.kt | 22 +++++++ .../messages/SyncMessageProcessor.kt | 23 +++++-- .../webrtc/GroupConnectedActionProcessor.java | 4 +- .../IncomingGroupCallActionProcessor.java | 4 ++ .../service/webrtc/SignalCallManager.java | 33 +++++++--- .../service/webrtc/WebRtcActionProcessor.java | 2 +- .../service/webrtc/WebRtcInteractor.java | 6 +- 8 files changed, 137 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/CallTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/CallTable.kt index 7ed51e0ebf..8b2cdd9777 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/CallTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/CallTable.kt @@ -367,6 +367,28 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl Log.d(TAG, "Transitioned group call ${call.callId} from ${call.event} to $newEvent") } + fun declineIncomingGroupCall(call: Call) { + checkIsGroupOrAdHocCall(call) + check(call.direction == Direction.INCOMING) + + val newEvent = when (call.event) { + Event.RINGING, Event.MISSED -> Event.DECLINED + else -> { + Log.d(TAG, "Call in state ${call.event} cannot be transitioned by DECLINED") + return + } + } + + writableDatabase + .update(TABLE_NAME) + .values(EVENT to Event.serialize(newEvent)) + .run() + + ApplicationDependencies.getMessageNotifier().updateNotification(context) + ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() + Log.d(TAG, "Transitioned group call ${call.callId} from ${call.event} to $newEvent") + } + fun insertAcceptedGroupCall( callId: Long, recipientId: RecipientId, @@ -410,6 +432,46 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() } + fun insertDeclinedGroupCall( + callId: Long, + recipientId: RecipientId, + timestamp: Long + ) { + val recipient = Recipient.resolved(recipientId) + val type = if (recipient.isCallLink) Type.AD_HOC_CALL else Type.GROUP_CALL + + writableDatabase.withinTransaction { db -> + val messageId: MessageId? = if (type == Type.GROUP_CALL) { + SignalDatabase.messages.insertGroupCall( + groupRecipientId = recipientId, + sender = Recipient.self().id, + timestamp, + "", + emptyList(), + false + ) + } else { + null + } + + db + .insertInto(TABLE_NAME) + .values( + CALL_ID to callId, + MESSAGE_ID to messageId?.id, + PEER to recipientId.toLong(), + EVENT to Event.serialize(Event.DECLINED), + TYPE to Type.serialize(type), + DIRECTION to Direction.serialize(Direction.INCOMING), + TIMESTAMP to timestamp, + RINGER to null + ) + .run(SQLiteDatabase.CONFLICT_ABORT) + } + + ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() + } + fun insertOrUpdateGroupCallFromExternalEvent( groupRecipientId: RecipientId, sender: RecipientId, diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/CallSyncEventJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/CallSyncEventJob.kt index 5df5f5c27e..493f140230 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/CallSyncEventJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/CallSyncEventJob.kt @@ -46,6 +46,21 @@ class CallSyncEventJob private constructor( ) } + @JvmStatic + fun createForNotAccepted(conversationRecipientId: RecipientId, callId: Long, isIncoming: Boolean): CallSyncEventJob { + return CallSyncEventJob( + getParameters(), + listOf( + CallSyncEventJobRecord( + recipientId = conversationRecipientId.toLong(), + callId = callId, + direction = CallTable.Direction.serialize(if (isIncoming) CallTable.Direction.INCOMING else CallTable.Direction.OUTGOING), + event = CallTable.Event.serialize(CallTable.Event.NOT_ACCEPTED) + ) + ) + ) + } + private fun createForDelete(calls: List): CallSyncEventJob { return CallSyncEventJob( getParameters(), @@ -129,6 +144,13 @@ class CallSyncEventJob private constructor( isVideoCall = callType != CallTable.Type.AUDIO_CALL ) + CallTable.Event.NOT_ACCEPTED -> CallEventSyncMessageUtil.createNotAcceptedSyncMessage( + remotePeer = RemotePeer(callSyncEvent.deserializeRecipientId(), CallId(callSyncEvent.callId)), + timestamp = syncTimestamp, + isOutgoing = callSyncEvent.deserializeDirection() == CallTable.Direction.OUTGOING, + isVideoCall = callType != CallTable.Type.AUDIO_CALL + ) + CallTable.Event.DELETE -> CallEventSyncMessageUtil.createDeleteCallEvent( remotePeer = RemotePeer(callSyncEvent.deserializeRecipientId(), CallId(callSyncEvent.callId)), timestamp = syncTimestamp, diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt index 19f9647968..0b3b20cd1c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt @@ -1314,7 +1314,7 @@ object SyncMessageProcessor { when (event) { CallTable.Event.DELETE -> SignalDatabase.calls.deleteGroupCall(call) CallTable.Event.ACCEPTED -> { - if (call.timestamp < timestamp) { + if (call.timestamp > timestamp) { SignalDatabase.calls.setTimestamp(call.callId, recipient.id, timestamp) } if (callEvent.direction == SyncMessage.CallEvent.Direction.INCOMING) { @@ -1323,15 +1323,30 @@ object SyncMessageProcessor { warn(envelopeTimestamp, "Invalid direction OUTGOING for event ACCEPTED") } } - CallTable.Event.NOT_ACCEPTED -> warn("Unsupported event type $event. Ignoring. timestamp: $timestamp type: $type direction: $direction event: $event hasPeer: $hasConversationId") + CallTable.Event.NOT_ACCEPTED -> { + if (call.timestamp > timestamp) { + SignalDatabase.calls.setTimestamp(call.callId, recipient.id, timestamp) + } + if (callEvent.direction == SyncMessage.CallEvent.Direction.INCOMING) { + SignalDatabase.calls.declineIncomingGroupCall(call) + } else { + warn(envelopeTimestamp, "Invalid direction OUTGOING for event NOT_ACCEPTED") + } + } else -> warn("Unsupported event type $event. Ignoring. timestamp: $timestamp type: $type direction: $direction event: $event hasPeer: $hasConversationId") } } else { when (event) { CallTable.Event.DELETE -> SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(callEvent.id!!, recipient.id, direction, timestamp) CallTable.Event.ACCEPTED -> SignalDatabase.calls.insertAcceptedGroupCall(callEvent.id!!, recipient.id, direction, timestamp) - CallTable.Event.NOT_ACCEPTED -> warn("Unsupported event type $event. Ignoring. timestamp: $timestamp type: $type direction: $direction event: $event hasPeer: $hasConversationId") - else -> warn("Unsupported event type $event. Ignoring. timestamp: $timestamp type: $type direction: $direction event: $event hasPeer: $hasConversationId") + CallTable.Event.NOT_ACCEPTED -> { + if (callEvent.direction == SyncMessage.CallEvent.Direction.INCOMING) { + SignalDatabase.calls.insertDeclinedGroupCall(callEvent.id!!, recipient.id, timestamp) + } else { + warn(envelopeTimestamp, "Invalid direction OUTGOING for event NOT_ACCEPTED for non-existing call") + } + } + else -> warn("Unsupported event type $event. Ignoring. timestamp: $timestamp type: $type direction: $direction event: $event hasPeer: $hasConversationId call: null") } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java index 70ff27af89..2c2e92e980 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java @@ -155,7 +155,7 @@ public class GroupConnectedActionProcessor extends GroupActionProcessor { boolean remoteUserRangTheCall = currentState.getCallSetupState(RemotePeer.GROUP_CALL_ID).getRingerRecipient() != Recipient.self(); String eraId = WebRtcUtil.getGroupCallEraId(groupCall); - webRtcInteractor.sendGroupCallMessage(currentState.getCallInfoState().getCallRecipient(), eraId, remoteUserRangTheCall, true); + webRtcInteractor.sendGroupCallMessage(currentState.getCallInfoState().getCallRecipient(), eraId, null, remoteUserRangTheCall, true); List members = new ArrayList<>(peekInfo.getJoinedMembers()); if (!members.contains(SignalStore.account().requireAci().getRawUuid())) { @@ -182,7 +182,7 @@ public class GroupConnectedActionProcessor extends GroupActionProcessor { } String eraId = WebRtcUtil.getGroupCallEraId(groupCall); - webRtcInteractor.sendGroupCallMessage(currentState.getCallInfoState().getCallRecipient(), eraId, false, false); + webRtcInteractor.sendGroupCallMessage(currentState.getCallInfoState().getCallRecipient(), eraId, null, false, false); List members = Stream.of(currentState.getCallInfoState().getRemoteCallParticipants()).map(p -> p.getRecipient().requireServiceId().getRawUuid()).toList(); webRtcInteractor.updateGroupCallUpdateMessage(currentState.getCallInfoState().getCallRecipient().getId(), eraId, members, false); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingGroupCallActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingGroupCallActionProcessor.java index 4befdbdc40..cabe78a8da 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingGroupCallActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingGroupCallActionProcessor.java @@ -6,6 +6,7 @@ import androidx.annotation.NonNull; import org.signal.core.util.logging.Log; import org.signal.ringrtc.CallException; +import org.signal.ringrtc.CallId; import org.signal.ringrtc.CallManager; import org.signal.ringrtc.GroupCall; import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink; @@ -233,6 +234,8 @@ public final class IncomingGroupCallActionProcessor extends DeviceAwareActionPro @Override protected @NonNull WebRtcServiceState handleDenyCall(@NonNull WebRtcServiceState currentState) { + Log.i(TAG, "handleDenyCall():"); + Recipient recipient = currentState.getCallInfoState().getCallRecipient(); Optional groupId = recipient.getGroupId(); long ringId = currentState.getCallSetupState(RemotePeer.GROUP_CALL_ID).getRingId(); @@ -252,6 +255,7 @@ public final class IncomingGroupCallActionProcessor extends DeviceAwareActionPro Log.w(TAG, "Error while trying to cancel ring " + ringId, e); } + webRtcInteractor.sendGroupCallMessage(currentState.getCallInfoState().getCallRecipient(), null, new CallId(ringId), true, false); webRtcInteractor.updatePhoneState(LockManager.PhoneState.PROCESSING); webRtcInteractor.stopAudio(false); webRtcInteractor.updatePhoneState(LockManager.PhoneState.IDLE); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java index 4cc8c75543..2d999d5d01 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java @@ -1009,7 +1009,9 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall. }); } - public void sendGroupCallUpdateMessage(@NonNull Recipient recipient, @Nullable String groupCallEraId, boolean isIncoming, boolean isJoinEvent) { + public void sendGroupCallUpdateMessage(@NonNull Recipient recipient, @Nullable String groupCallEraId, final @Nullable CallId callId, boolean isIncoming, boolean isJoinEvent) { + Log.i(TAG, "sendGroupCallUpdateMessage id: " + recipient.getId() + " era: " + groupCallEraId + " isIncoming: " + isIncoming + " isJoinEvent: " + isJoinEvent); + if (recipient.isCallLink()) { Log.i(TAG, "sendGroupCallUpdateMessage -- ignoring for call link"); return; @@ -1018,15 +1020,28 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall. SignalExecutors.BOUNDED.execute(() -> { GroupCallUpdateSendJob updateSendJob = GroupCallUpdateSendJob.create(recipient.getId(), groupCallEraId); JobManager.Chain chain = ApplicationDependencies.getJobManager().startChain(updateSendJob); + CallId callIdLocal = callId; - if (isJoinEvent && groupCallEraId != null) { - chain.then(CallSyncEventJob.createForJoin( - recipient.getId(), - CallId.fromEra(groupCallEraId).longValue(), - isIncoming - )); - } else if (isJoinEvent) { - Log.w(TAG, "Can't send join event sync message without an era id."); + if (callIdLocal == null && groupCallEraId != null) { + callIdLocal = CallId.fromEra(groupCallEraId); + } + + if (callIdLocal != null) { + if (isJoinEvent) { + chain.then(CallSyncEventJob.createForJoin( + recipient.getId(), + callIdLocal.longValue(), + isIncoming + )); + } else if (isIncoming) { + chain.then(CallSyncEventJob.createForNotAccepted( + recipient.getId(), + callIdLocal.longValue(), + isIncoming + )); + } + } else { + Log.w(TAG, "Can't send sync message without a call id. isIncoming: " + isIncoming + " isJoinEvent: " + isJoinEvent); } chain.enqueue(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java index ad5a9c585d..8c5b5f051c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java @@ -828,7 +828,7 @@ public abstract class WebRtcActionProcessor { Recipient recipient = currentState.getCallInfoState().getCallRecipient(); if (recipient != null && currentState.getCallInfoState().getGroupCallState().isConnected()) { - webRtcInteractor.sendGroupCallMessage(recipient, WebRtcUtil.getGroupCallEraId(groupCall), false, false); + webRtcInteractor.sendGroupCallMessage(recipient, WebRtcUtil.getGroupCallEraId(groupCall), null, false, false); } currentState = currentState.builder() diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java index ffee8f9265..8e53b1785a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java @@ -5,8 +5,8 @@ import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; +import org.signal.ringrtc.CallId; import org.signal.ringrtc.CallManager; import org.signal.ringrtc.GroupCall; import org.thoughtcrime.securesms.groups.GroupId; @@ -85,8 +85,8 @@ public class WebRtcInteractor { signalCallManager.sendCallMessage(remotePeer, callMessage); } - void sendGroupCallMessage(@NonNull Recipient recipient, @Nullable String groupCallEraId, boolean isIncoming, boolean isJoinEvent) { - signalCallManager.sendGroupCallUpdateMessage(recipient, groupCallEraId, isIncoming, isJoinEvent); + void sendGroupCallMessage(@NonNull Recipient recipient, @Nullable String groupCallEraId, @Nullable CallId callId, boolean isIncoming, boolean isJoinEvent) { + signalCallManager.sendGroupCallUpdateMessage(recipient, groupCallEraId, callId, isIncoming, isJoinEvent); } void updateGroupCallUpdateMessage(@NonNull RecipientId groupId, @Nullable String groupCallEraId, @NonNull Collection joinedMembers, boolean isCallFull) {