Add support for group call disposition.
Co-authored-by: Cody Henthorne <cody@signal.org>
This commit is contained in:
parent
e94a84d4ec
commit
f9548dcffe
40 changed files with 2165 additions and 340 deletions
|
@ -0,0 +1,750 @@
|
|||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.signal.ringrtc.CallId
|
||||
import org.signal.ringrtc.CallManager
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CallTableTest {
|
||||
|
||||
@get:Rule
|
||||
val harness = SignalActivityRule()
|
||||
|
||||
@Test
|
||||
fun givenACall_whenISetTimestamp_thenIExpectUpdatedTimestamp() {
|
||||
val callId = 1L
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
callId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.INCOMING,
|
||||
now
|
||||
)
|
||||
|
||||
SignalDatabase.calls.setTimestamp(callId, -1L)
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(-1L, call?.timestamp)
|
||||
|
||||
val messageRecord = SignalDatabase.messages.getMessageRecord(call!!.messageId!!)
|
||||
assertEquals(-1L, messageRecord.dateReceived)
|
||||
assertEquals(-1L, messageRecord.dateSent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenPreExistingEvent_whenIDeleteGroupCall_thenIMarkDeletedAndSetTimestamp() {
|
||||
val callId = 1L
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
callId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.INCOMING,
|
||||
now
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
SignalDatabase.calls.deleteGroupCall(call!!)
|
||||
|
||||
val deletedCall = SignalDatabase.calls.getCallById(callId)
|
||||
val oldestDeletionTimestamp = SignalDatabase.calls.getOldestDeletionTimestamp()
|
||||
|
||||
assertEquals(CallTable.Event.DELETE, deletedCall?.event)
|
||||
assertNotEquals(0L, oldestDeletionTimestamp)
|
||||
assertNull(deletedCall!!.messageId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenNoPreExistingEvent_whenIDeleteGroupCall_thenIInsertAndMarkCallDeleted() {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(
|
||||
callId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.OUTGOING,
|
||||
System.currentTimeMillis()
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
|
||||
val oldestDeletionTimestamp = SignalDatabase.calls.getOldestDeletionTimestamp()
|
||||
|
||||
assertEquals(CallTable.Event.DELETE, call?.event)
|
||||
assertNotEquals(oldestDeletionTimestamp, 0)
|
||||
assertNull(call?.messageId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenNoPriorEvent_whenIInsertAcceptedOutgoingGroupCall_thenIExpectLocalRingerAndOutgoingRing() {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
callId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.OUTGOING,
|
||||
System.currentTimeMillis()
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.OUTGOING_RING, call?.event)
|
||||
assertEquals(harness.self.id, call?.ringerRecipient)
|
||||
assertNotNull(call?.messageId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenNoPriorEvent_whenIInsertAcceptedIncomingGroupCall_thenIExpectJoined() {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
callId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.INCOMING,
|
||||
System.currentTimeMillis()
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.JOINED, call?.event)
|
||||
assertNull(call?.ringerRecipient)
|
||||
assertNotNull(call?.messageId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenARingingCall_whenIAcceptedIncomingGroupCall_thenIExpectAccepted() {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
ringId = callId,
|
||||
groupRecipientId = harness.others[0],
|
||||
ringerRecipient = harness.others[1],
|
||||
dateReceived = System.currentTimeMillis(),
|
||||
ringState = CallManager.RingUpdate.REQUESTED
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.RINGING, call?.event)
|
||||
|
||||
SignalDatabase.calls.acceptIncomingGroupCall(
|
||||
call!!
|
||||
)
|
||||
|
||||
val acceptedCall = SignalDatabase.calls.getCallById(callId)
|
||||
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAMissedCall_whenIAcceptedIncomingGroupCall_thenIExpectAccepted() {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
ringId = callId,
|
||||
groupRecipientId = harness.others[0],
|
||||
ringerRecipient = harness.others[1],
|
||||
dateReceived = System.currentTimeMillis(),
|
||||
ringState = CallManager.RingUpdate.EXPIRED_REQUEST
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
|
||||
SignalDatabase.calls.acceptIncomingGroupCall(
|
||||
call!!
|
||||
)
|
||||
|
||||
val acceptedCall = SignalDatabase.calls.getCallById(callId)
|
||||
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenADeclinedCall_whenIAcceptedIncomingGroupCall_thenIExpectAccepted() {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
ringId = callId,
|
||||
groupRecipientId = harness.others[0],
|
||||
ringerRecipient = harness.others[1],
|
||||
dateReceived = System.currentTimeMillis(),
|
||||
ringState = CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.DECLINED, call?.event)
|
||||
|
||||
SignalDatabase.calls.acceptIncomingGroupCall(
|
||||
call!!
|
||||
)
|
||||
|
||||
val acceptedCall = SignalDatabase.calls.getCallById(callId)
|
||||
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAGenericGroupCall_whenIAcceptedIncomingGroupCall_thenIExpectAccepted() {
|
||||
val era = "aaa"
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = System.currentTimeMillis(),
|
||||
peekGroupCallEraId = "aaa",
|
||||
peekJoinedUuids = emptyList(),
|
||||
isCallFull = false
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
|
||||
|
||||
SignalDatabase.calls.acceptIncomingGroupCall(
|
||||
call!!
|
||||
)
|
||||
|
||||
val acceptedCall = SignalDatabase.calls.getCallById(callId)
|
||||
assertEquals(CallTable.Event.JOINED, acceptedCall?.event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenNoPriorCallEvent_whenIReceiveAGroupCallUpdateMessage_thenIExpectAGenericGroupCall() {
|
||||
val era = "aaa"
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = System.currentTimeMillis(),
|
||||
peekGroupCallEraId = "aaa",
|
||||
peekJoinedUuids = emptyList(),
|
||||
isCallFull = false
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAPriorCallEventWithNewerTimestamp_whenIReceiveAGroupCallUpdateMessage_thenIExpectAnUpdatedTimestamp() {
|
||||
val era = "aaa"
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = now,
|
||||
peekGroupCallEraId = "aaa",
|
||||
peekJoinedUuids = emptyList(),
|
||||
isCallFull = false
|
||||
)
|
||||
|
||||
SignalDatabase.calls.getCallById(callId).let {
|
||||
assertNotNull(it)
|
||||
assertEquals(now, it?.timestamp)
|
||||
}
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = 1L,
|
||||
peekGroupCallEraId = "aaa",
|
||||
peekJoinedUuids = emptyList(),
|
||||
isCallFull = false
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
|
||||
assertEquals(1L, call?.timestamp)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenADeletedCallEvent_whenIReceiveARingUpdate_thenIIgnoreTheRingUpdate() {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(
|
||||
callId = callId,
|
||||
recipientId = harness.others[0],
|
||||
direction = CallTable.Direction.INCOMING,
|
||||
timestamp = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
ringId = callId,
|
||||
groupRecipientId = harness.others[0],
|
||||
ringerRecipient = harness.others[1],
|
||||
dateReceived = System.currentTimeMillis(),
|
||||
ringState = CallManager.RingUpdate.REQUESTED
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.DELETE, call?.event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAGenericCallEvent_whenRingRequested_thenISetRingerAndMoveToRingingState() {
|
||||
val era = "aaa"
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = now,
|
||||
peekGroupCallEraId = "aaa",
|
||||
peekJoinedUuids = emptyList(),
|
||||
isCallFull = false
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.REQUESTED
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.RINGING, call?.event)
|
||||
assertEquals(harness.others[1], call?.ringerRecipient)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAJoinedCallEvent_whenRingRequested_thenISetRingerAndMoveToRingingState() {
|
||||
val era = "aaa"
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
callId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.INCOMING,
|
||||
now
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.REQUESTED
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.ACCEPTED, call?.event)
|
||||
assertEquals(harness.others[1], call?.ringerRecipient)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAGenericCallEvent_whenRingExpired_thenISetRingerAndMoveToMissedState() {
|
||||
val era = "aaa"
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = now,
|
||||
peekGroupCallEraId = "aaa",
|
||||
peekJoinedUuids = emptyList(),
|
||||
isCallFull = false
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.EXPIRED_REQUEST
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
assertEquals(harness.others[1], call?.ringerRecipient)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenARingingCallEvent_whenRingExpired_thenISetRingerAndMoveToMissedState() {
|
||||
val era = "aaa"
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = now,
|
||||
peekGroupCallEraId = "aaa",
|
||||
peekJoinedUuids = emptyList(),
|
||||
isCallFull = false
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.REQUESTED
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.EXPIRED_REQUEST
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
assertEquals(harness.others[1], call?.ringerRecipient)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAJoinedCallEvent_whenRingIsCancelledBecauseUserIsBusyLocally_thenIMoveToAcceptedState() {
|
||||
val era = "aaa"
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
callId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.INCOMING,
|
||||
now
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.BUSY_LOCALLY
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.ACCEPTED, call?.event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAJoinedCallEvent_whenRingIsCancelledBecauseUserIsBusyOnAnotherDevice_thenIMoveToAcceptedState() {
|
||||
val era = "aaa"
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
callId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.INCOMING,
|
||||
now
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.ACCEPTED, call?.event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenARingingCallEvent_whenRingCancelledBecauseUserIsBusyLocally_thenIMoveToMissedState() {
|
||||
val era = "aaa"
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = now,
|
||||
peekGroupCallEraId = "aaa",
|
||||
peekJoinedUuids = emptyList(),
|
||||
isCallFull = false
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.REQUESTED
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.BUSY_LOCALLY
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenARingingCallEvent_whenRingCancelledBecauseUserIsBusyOnAnotherDevice_thenIMoveToMissedState() {
|
||||
val era = "aaa"
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = now,
|
||||
peekGroupCallEraId = "aaa",
|
||||
peekJoinedUuids = emptyList(),
|
||||
isCallFull = false
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.REQUESTED
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenACallEvent_whenRingIsAcceptedOnAnotherDevice_thenIMoveToAcceptedState() {
|
||||
val era = "aaa"
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = now,
|
||||
peekGroupCallEraId = "aaa",
|
||||
peekJoinedUuids = emptyList(),
|
||||
isCallFull = false
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.ACCEPTED, call?.event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenARingingCallEvent_whenRingDeclinedOnAnotherDevice_thenIMoveToDeclinedState() {
|
||||
val era = "aaa"
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.REQUESTED
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.DECLINED, call?.event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAMissedCallEvent_whenRingDeclinedOnAnotherDevice_thenIMoveToDeclinedState() {
|
||||
val era = "aaa"
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.EXPIRED_REQUEST
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.DECLINED, call?.event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAnOutgoingRingCallEvent_whenRingDeclinedOnAnotherDevice_thenIDoNotChangeState() {
|
||||
val era = "aaa"
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
callId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.OUTGOING,
|
||||
System.currentTimeMillis()
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.OUTGOING_RING, call?.event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenNoPriorEvent_whenRingRequested_thenICreateAnEventInTheRingingStateAndSetRinger() {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.REQUESTED
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.RINGING, call?.event)
|
||||
assertEquals(harness.others[1], call?.ringerRecipient)
|
||||
assertNotNull(call?.messageId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenNoPriorEvent_whenRingExpired_thenICreateAnEventInTheMissedStateAndSetRinger() {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.EXPIRED_REQUEST
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
assertEquals(harness.others[1], call?.ringerRecipient)
|
||||
assertNotNull(call?.messageId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenNoPriorEvent_whenRingCancelledByRinger_thenICreateAnEventInTheMissedStateAndSetRinger() {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.CANCELLED_BY_RINGER
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
assertEquals(harness.others[1], call?.ringerRecipient)
|
||||
assertNotNull(call?.messageId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenNoPriorEvent_whenRingCancelledBecauseUserIsBusyLocally_thenICreateAnEventInTheMissedState() {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.BUSY_LOCALLY
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
assertNotNull(call?.messageId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenNoPriorEvent_whenRingCancelledBecauseUserIsBusyOnAnotherDevice_thenICreateAnEventInTheMissedState() {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
assertNotNull(call?.messageId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenNoPriorEvent_whenRingAcceptedOnAnotherDevice_thenICreateAnEventInTheAcceptedState() {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.ACCEPTED, call?.event)
|
||||
assertNotNull(call?.messageId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenNoPriorEvent_whenRingDeclinedOnAnotherDevice_thenICreateAnEventInTheDeclinedState() {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.DECLINED, call?.event)
|
||||
assertNotNull(call?.messageId)
|
||||
}
|
||||
}
|
|
@ -197,6 +197,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||
.addNonBlocking(() -> ApplicationDependencies.getGiphyMp4Cache().onAppStart(this))
|
||||
.addNonBlocking(this::ensureProfileUploaded)
|
||||
.addNonBlocking(() -> ApplicationDependencies.getExpireStoriesManager().scheduleIfNecessary())
|
||||
.addPostRender(() -> ApplicationDependencies.getDeletedCallEventManager().scheduleIfNecessary())
|
||||
.addPostRender(() -> RateLimitUtil.retryAllRateLimitedMessages(this))
|
||||
.addPostRender(this::initializeExpiringMessageManager)
|
||||
.addPostRender(() -> SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this)))
|
||||
|
@ -214,7 +215,6 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||
.addPostRender(StoryOnboardingDownloadJob.Companion::enqueueIfNeeded)
|
||||
.addPostRender(PnpInitializeDevicesJob::enqueueIfNecessary)
|
||||
.addPostRender(() -> ApplicationDependencies.getExoPlayerPool().getPoolStats().getMaxUnreserved())
|
||||
.addPostRender(() -> SignalDatabase.groupCallRings().removeOldRings())
|
||||
.addPostRender(() -> ApplicationDependencies.getRecipientCache().warmUp())
|
||||
.execute();
|
||||
|
||||
|
|
|
@ -186,6 +186,10 @@ class CallLogAdapter(
|
|||
binding.callType.setImageResource(R.drawable.symbol_video_24)
|
||||
binding.callType.setOnClickListener { onStartVideoCallClicked(peer) }
|
||||
}
|
||||
|
||||
CallTable.Type.GROUP_CALL, CallTable.Type.AD_HOC_CALL -> {
|
||||
// TODO [alex] -- Group call button
|
||||
}
|
||||
}
|
||||
|
||||
binding.callType.visible = true
|
||||
|
|
|
@ -72,7 +72,7 @@ class CallLogContextMenu(
|
|||
iconRes = R.drawable.symbol_info_24,
|
||||
title = fragment.getString(R.string.CallContextMenu__info)
|
||||
) {
|
||||
val intent = ConversationSettingsActivity.forCall(fragment.requireContext(), call.peer, longArrayOf(call.call.messageId))
|
||||
val intent = ConversationSettingsActivity.forCall(fragment.requireContext(), call.peer, longArrayOf(call.call.messageId!!))
|
||||
fragment.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.conversationlist.chatfilter.ConversationListFi
|
|||
import org.thoughtcrime.securesms.conversationlist.chatfilter.FilterLerp
|
||||
import org.thoughtcrime.securesms.conversationlist.chatfilter.FilterPullState
|
||||
import org.thoughtcrime.securesms.databinding.CallLogFragmentBinding
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.main.Material3OnScrollHelperBinder
|
||||
import org.thoughtcrime.securesms.main.SearchBinder
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
@ -167,6 +168,7 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
|||
override fun onResume() {
|
||||
super.onResume()
|
||||
initializeSearchAction()
|
||||
ApplicationDependencies.getDeletedCallEventManager().scheduleIfNecessary()
|
||||
}
|
||||
|
||||
private fun initializeTapToScrollToTop() {
|
||||
|
@ -270,7 +272,7 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
|||
if (viewModel.selectionStateSnapshot.isNotEmpty(binding.recycler.adapter!!.itemCount)) {
|
||||
viewModel.toggleSelected(callLogRow.id)
|
||||
} else {
|
||||
val intent = ConversationSettingsActivity.forCall(requireContext(), callLogRow.peer, longArrayOf(callLogRow.call.messageId))
|
||||
val intent = ConversationSettingsActivity.forCall(requireContext(), callLogRow.peer, longArrayOf(callLogRow.call.messageId!!))
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,18 +41,18 @@ class CallLogRepository : CallLogPagedDataSource.CallRepository {
|
|||
}
|
||||
|
||||
fun deleteSelectedCallLogs(
|
||||
selectedMessageIds: Set<Long>
|
||||
selectedCallIds: Set<Long>
|
||||
): Completable {
|
||||
return Completable.fromAction {
|
||||
SignalDatabase.messages.deleteCallUpdates(selectedMessageIds)
|
||||
SignalDatabase.calls.deleteCallEvents(selectedCallIds)
|
||||
}.observeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun deleteAllCallLogsExcept(
|
||||
selectedMessageIds: Set<Long>
|
||||
selectedCallIds: Set<Long>
|
||||
): Completable {
|
||||
return Completable.fromAction {
|
||||
SignalDatabase.messages.deleteAllCallUpdatesExcept(selectedMessageIds)
|
||||
SignalDatabase.calls.deleteAllCallEventsExcept(selectedCallIds)
|
||||
}.observeOn(Schedulers.io())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ sealed class CallLogRow {
|
|||
val call: CallTable.Call,
|
||||
val peer: Recipient,
|
||||
val date: Long,
|
||||
override val id: Id = Id.Call(call.messageId)
|
||||
override val id: Id = Id.Call(call.callId)
|
||||
) : CallLogRow()
|
||||
|
||||
/**
|
||||
|
@ -28,7 +28,7 @@ sealed class CallLogRow {
|
|||
}
|
||||
|
||||
sealed class Id {
|
||||
data class Call(val messageId: Long) : Id()
|
||||
data class Call(val callId: Long) : Id()
|
||||
object ClearFilter : Id()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,15 +28,15 @@ class CallLogStagedDeletion(
|
|||
}
|
||||
|
||||
isCommitted = true
|
||||
val messageIds = stateSnapshot.selected()
|
||||
val callIds = stateSnapshot.selected()
|
||||
.filterIsInstance<CallLogRow.Id.Call>()
|
||||
.map { it.messageId }
|
||||
.map { it.callId }
|
||||
.toSet()
|
||||
|
||||
if (stateSnapshot.isExclusionary()) {
|
||||
repository.deleteAllCallLogsExcept(messageIds).subscribe()
|
||||
repository.deleteAllCallLogsExcept(callIds).subscribe()
|
||||
} else {
|
||||
repository.deleteSelectedCallLogs(messageIds).subscribe()
|
||||
repository.deleteSelectedCallLogs(callIds).subscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.signal.core.util.logging.Log
|
|||
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember
|
||||
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery
|
||||
import org.thoughtcrime.securesms.database.CallTable
|
||||
import org.thoughtcrime.securesms.database.MediaTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.GroupRecord
|
||||
|
@ -39,12 +40,16 @@ class ConversationSettingsRepository(
|
|||
private val groupManagementRepository: GroupManagementRepository = GroupManagementRepository(context)
|
||||
) {
|
||||
|
||||
fun getCallEvents(callMessageIds: LongArray): Single<List<MessageRecord>> {
|
||||
fun getCallEvents(callMessageIds: LongArray): Single<List<Pair<CallTable.Call, MessageRecord>>> {
|
||||
return if (callMessageIds.isEmpty()) {
|
||||
Single.just(emptyList())
|
||||
} else {
|
||||
Single.fromCallable {
|
||||
SignalDatabase.messages.getMessages(callMessageIds.toList()).iterator().asSequence().toList()
|
||||
val callMap = SignalDatabase.calls.getCalls(callMessageIds.toList())
|
||||
SignalDatabase.messages.getMessages(callMessageIds.toList()).iterator().asSequence()
|
||||
.filter { callMap.containsKey(it.id) }
|
||||
.map { callMap[it.id]!! to it }
|
||||
.toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ sealed class ConversationSettingsViewModel(
|
|||
}
|
||||
|
||||
store.update(repository.getCallEvents(callMessageIds).toObservable()) { callRecords, state ->
|
||||
state.copy(calls = callRecords.map { CallPreference.Model(it) })
|
||||
state.copy(calls = callRecords.map { (call, messageRecord) -> CallPreference.Model(call, messageRecord) })
|
||||
}
|
||||
|
||||
store.update(sharedMedia) { cursor, state ->
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components.settings.conversation.preferences
|
|||
|
||||
import androidx.annotation.DrawableRes
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.database.CallTable
|
||||
import org.thoughtcrime.securesms.database.MessageTypes
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.databinding.ConversationSettingsCallPreferenceItemBinding
|
||||
|
@ -21,12 +22,14 @@ object CallPreference {
|
|||
}
|
||||
|
||||
class Model(
|
||||
val call: CallTable.Call,
|
||||
val record: MessageRecord
|
||||
) : MappingModel<Model> {
|
||||
override fun areItemsTheSame(newItem: Model): Boolean = record.id == newItem.record.id
|
||||
|
||||
override fun areContentsTheSame(newItem: Model): Boolean {
|
||||
return record.type == newItem.record.type &&
|
||||
return call == newItem.call &&
|
||||
record.type == newItem.record.type &&
|
||||
record.isOutgoing == newItem.record.isOutgoing &&
|
||||
record.timestamp == newItem.record.timestamp &&
|
||||
record.id == newItem.record.id
|
||||
|
@ -35,30 +38,42 @@ object CallPreference {
|
|||
|
||||
private class ViewHolder(binding: ConversationSettingsCallPreferenceItemBinding) : BindingViewHolder<Model, ConversationSettingsCallPreferenceItemBinding>(binding) {
|
||||
override fun bind(model: Model) {
|
||||
binding.callIcon.setImageResource(getCallIcon(model.record))
|
||||
binding.callType.text = getCallType(model.record)
|
||||
binding.callIcon.setImageResource(getCallIcon(model.call))
|
||||
binding.callType.text = getCallType(model.call)
|
||||
binding.callTime.text = getCallTime(model.record)
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
private fun getCallIcon(messageRecord: MessageRecord): Int {
|
||||
return when (messageRecord.type) {
|
||||
private fun getCallIcon(call: CallTable.Call): Int {
|
||||
return when (call.messageType) {
|
||||
MessageTypes.MISSED_VIDEO_CALL_TYPE, MessageTypes.MISSED_AUDIO_CALL_TYPE -> R.drawable.symbol_missed_incoming_24
|
||||
MessageTypes.INCOMING_AUDIO_CALL_TYPE, MessageTypes.INCOMING_VIDEO_CALL_TYPE -> R.drawable.symbol_arrow_downleft_24
|
||||
MessageTypes.OUTGOING_AUDIO_CALL_TYPE, MessageTypes.OUTGOING_VIDEO_CALL_TYPE -> R.drawable.symbol_arrow_upright_24
|
||||
else -> error("Unexpected type ${messageRecord.type}")
|
||||
MessageTypes.GROUP_CALL_TYPE -> when {
|
||||
call.event == CallTable.Event.MISSED -> R.drawable.symbol_missed_incoming_24
|
||||
call.direction == CallTable.Direction.INCOMING -> R.drawable.symbol_arrow_downleft_24
|
||||
call.direction == CallTable.Direction.OUTGOING -> R.drawable.symbol_arrow_upright_24
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
else -> error("Unexpected type ${call.type}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCallType(messageRecord: MessageRecord): String {
|
||||
val id = when (messageRecord.type) {
|
||||
private fun getCallType(call: CallTable.Call): String {
|
||||
val id = when (call.messageType) {
|
||||
MessageTypes.MISSED_VIDEO_CALL_TYPE -> R.string.MessageRecord_missed_voice_call
|
||||
MessageTypes.MISSED_AUDIO_CALL_TYPE -> R.string.MessageRecord_missed_video_call
|
||||
MessageTypes.INCOMING_AUDIO_CALL_TYPE -> R.string.MessageRecord_incoming_voice_call
|
||||
MessageTypes.INCOMING_VIDEO_CALL_TYPE -> R.string.MessageRecord_incoming_video_call
|
||||
MessageTypes.OUTGOING_AUDIO_CALL_TYPE -> R.string.MessageRecord_outgoing_voice_call
|
||||
MessageTypes.OUTGOING_VIDEO_CALL_TYPE -> R.string.MessageRecord_outgoing_video_call
|
||||
else -> error("Unexpected type ${messageRecord.type}")
|
||||
MessageTypes.GROUP_CALL_TYPE -> when {
|
||||
call.event == CallTable.Event.MISSED -> R.string.CallPreference__missed_group_call
|
||||
call.direction == CallTable.Direction.INCOMING -> R.string.CallPreference__incoming_group_call
|
||||
call.direction == CallTable.Direction.OUTGOING -> R.string.CallPreference__outgoing_group_call
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
else -> error("Unexpected type ${call.messageType}")
|
||||
}
|
||||
|
||||
return context.getString(id)
|
||||
|
|
|
@ -2,24 +2,34 @@ package org.thoughtcrime.securesms.database
|
|||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import androidx.annotation.Discouraged
|
||||
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.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.readToSingleLong
|
||||
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.signal.ringrtc.CallId
|
||||
import org.signal.ringrtc.CallManager.RingUpdate
|
||||
import org.thoughtcrime.securesms.calls.log.CallLogFilter
|
||||
import org.thoughtcrime.securesms.calls.log.CallLogRow
|
||||
import org.thoughtcrime.securesms.database.model.MessageId
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobs.CallSyncEventJob
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.CallEvent
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Contains details for each 1:1 call.
|
||||
|
@ -37,16 +47,23 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
private const val TYPE = "type"
|
||||
private const val DIRECTION = "direction"
|
||||
private const val EVENT = "event"
|
||||
private const val TIMESTAMP = "timestamp"
|
||||
private const val RINGER = "ringer"
|
||||
private const val DELETION_TIMESTAMP = "deletion_timestamp"
|
||||
|
||||
//language=sql
|
||||
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,
|
||||
$MESSAGE_ID INTEGER DEFAULT NULL REFERENCES ${MessageTable.TABLE_NAME} (${MessageTable.ID}) ON DELETE SET NULL,
|
||||
$PEER INTEGER DEFAULT NULL REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE,
|
||||
$TYPE INTEGER NOT NULL,
|
||||
$DIRECTION INTEGER NOT NULL,
|
||||
$EVENT INTEGER NOT NULL
|
||||
$EVENT INTEGER NOT NULL,
|
||||
$TIMESTAMP INTEGER NOT NULL,
|
||||
$RINGER INTEGER DEFAULT NULL,
|
||||
$DELETION_TIMESTAMP INTEGER DEFAULT 0
|
||||
)
|
||||
""".trimIndent()
|
||||
|
||||
|
@ -56,7 +73,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
)
|
||||
}
|
||||
|
||||
fun insertCall(callId: Long, timestamp: Long, peer: RecipientId, type: Type, direction: Direction, event: Event) {
|
||||
fun insertOneToOneCall(callId: Long, timestamp: Long, peer: RecipientId, type: Type, direction: Direction, event: Event) {
|
||||
val messageType: Long = Call.getMessageType(type, direction, event)
|
||||
|
||||
writableDatabase.withinTransaction {
|
||||
|
@ -68,7 +85,8 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
PEER to peer.serialize(),
|
||||
TYPE to Type.serialize(type),
|
||||
DIRECTION to Direction.serialize(direction),
|
||||
EVENT to Event.serialize(event)
|
||||
EVENT to Event.serialize(event),
|
||||
TIMESTAMP to timestamp
|
||||
)
|
||||
|
||||
writableDatabase.insert(TABLE_NAME, null, values)
|
||||
|
@ -79,7 +97,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
Log.i(TAG, "Inserted call: $callId type: $type direction: $direction event:$event")
|
||||
}
|
||||
|
||||
fun updateCall(callId: Long, event: Event): Call? {
|
||||
fun updateOneToOneCall(callId: Long, event: Event): Call? {
|
||||
return writableDatabase.withinTransaction {
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
|
@ -97,7 +115,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
if (call != null) {
|
||||
Log.i(TAG, "Updated call: $callId event: $event")
|
||||
|
||||
SignalDatabase.messages.updateCallLog(call.messageId, call.messageType)
|
||||
SignalDatabase.messages.updateCallLog(call.messageId!!, call.messageType)
|
||||
ApplicationDependencies.getMessageNotifier().updateNotification(context)
|
||||
}
|
||||
|
||||
|
@ -131,7 +149,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
val cursor = readableDatabase
|
||||
.select()
|
||||
.from(TABLE_NAME)
|
||||
.where(query.where, query.whereArgs)
|
||||
.where("$EVENT != ${Event.serialize(Event.DELETE)} AND ${query.where}", query.whereArgs)
|
||||
.run()
|
||||
|
||||
calls.putAll(cursor.readToList { c -> c.requireLong(MESSAGE_ID) to Call.deserialize(c) })
|
||||
|
@ -139,9 +157,536 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
return calls
|
||||
}
|
||||
|
||||
fun getOldestDeletionTimestamp(): Long {
|
||||
return writableDatabase
|
||||
.select(DELETION_TIMESTAMP)
|
||||
.from(TABLE_NAME)
|
||||
.where("$DELETION_TIMESTAMP > 0")
|
||||
.orderBy("$DELETION_TIMESTAMP DESC")
|
||||
.limit(1)
|
||||
.run()
|
||||
.readToSingleLong(0L)
|
||||
}
|
||||
|
||||
fun deleteCallEventsDeletedBefore(threshold: Long) {
|
||||
writableDatabase
|
||||
.delete(TABLE_NAME)
|
||||
.where("$DELETION_TIMESTAMP <= ?", threshold)
|
||||
.run()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
val where = "$TYPE != ? AND $DELETION_TIMESTAMP = 0 AND $MESSAGE_ID IS NULL"
|
||||
val type = Type.serialize(Type.AD_HOC_CALL)
|
||||
|
||||
val toSync = writableDatabase.withinTransaction { db ->
|
||||
val result = db
|
||||
.select()
|
||||
.from(TABLE_NAME)
|
||||
.where(where, type)
|
||||
.run()
|
||||
.readToList {
|
||||
Call.deserialize(it)
|
||||
}
|
||||
.toSet()
|
||||
|
||||
db
|
||||
.update(TABLE_NAME)
|
||||
.values(DELETION_TIMESTAMP to System.currentTimeMillis())
|
||||
.where(where, type)
|
||||
.run()
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
CallSyncEventJob.enqueueDeleteSyncEvents(toSync)
|
||||
ApplicationDependencies.getDeletedCallEventManager().scheduleIfNecessary()
|
||||
}
|
||||
|
||||
// region Group / Ad-Hoc Calling
|
||||
|
||||
fun deleteGroupCall(call: Call) {
|
||||
checkIsGroupOrAdHocCall(call)
|
||||
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db
|
||||
.update(TABLE_NAME)
|
||||
.values(
|
||||
EVENT to Event.serialize(Event.DELETE),
|
||||
DELETION_TIMESTAMP to System.currentTimeMillis()
|
||||
)
|
||||
.where("$CALL_ID = ?", call.callId)
|
||||
.run()
|
||||
|
||||
if (call.messageId != null) {
|
||||
SignalDatabase.messages.deleteMessage(call.messageId)
|
||||
}
|
||||
}
|
||||
|
||||
ApplicationDependencies.getMessageNotifier().updateNotification(context)
|
||||
Log.d(TAG, "Marked group call event for deletion: ${call.callId}")
|
||||
}
|
||||
|
||||
fun insertDeletedGroupCallFromSyncEvent(
|
||||
callId: Long,
|
||||
recipientId: RecipientId?,
|
||||
direction: Direction,
|
||||
timestamp: Long
|
||||
) {
|
||||
val type = if (recipientId != null) Type.GROUP_CALL else Type.AD_HOC_CALL
|
||||
|
||||
writableDatabase
|
||||
.insertInto(TABLE_NAME)
|
||||
.values(
|
||||
CALL_ID to callId,
|
||||
MESSAGE_ID to null,
|
||||
PEER to recipientId?.toLong(),
|
||||
EVENT to Event.serialize(Event.DELETE),
|
||||
TYPE to Type.serialize(type),
|
||||
DIRECTION to Direction.serialize(direction),
|
||||
TIMESTAMP to timestamp,
|
||||
DELETION_TIMESTAMP to System.currentTimeMillis()
|
||||
)
|
||||
.run()
|
||||
|
||||
ApplicationDependencies.getDeletedCallEventManager().scheduleIfNecessary()
|
||||
}
|
||||
|
||||
fun acceptIncomingGroupCall(call: Call) {
|
||||
checkIsGroupOrAdHocCall(call)
|
||||
check(call.direction == Direction.INCOMING)
|
||||
|
||||
val newEvent = when (call.event) {
|
||||
Event.RINGING, Event.MISSED, Event.DECLINED -> Event.ACCEPTED
|
||||
Event.GENERIC_GROUP_CALL -> Event.JOINED
|
||||
else -> {
|
||||
Log.d(TAG, "Call in state ${call.event} cannot be transitioned by ACCEPTED")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(EVENT to Event.serialize(newEvent))
|
||||
.run()
|
||||
|
||||
ApplicationDependencies.getMessageNotifier().updateNotification(context)
|
||||
Log.d(TAG, "Transitioned group call ${call.callId} from ${call.event} to $newEvent")
|
||||
}
|
||||
|
||||
fun insertAcceptedGroupCall(
|
||||
callId: Long,
|
||||
recipientId: RecipientId?,
|
||||
direction: Direction,
|
||||
timestamp: Long
|
||||
) {
|
||||
val type = if (recipientId != null) Type.GROUP_CALL else Type.AD_HOC_CALL
|
||||
val event = if (direction == Direction.OUTGOING) Event.OUTGOING_RING else Event.JOINED
|
||||
val ringer = if (direction == Direction.OUTGOING) Recipient.self().id.toLong() else null
|
||||
|
||||
writableDatabase.withinTransaction { db ->
|
||||
val messageId: MessageId? = if (recipientId != null) {
|
||||
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),
|
||||
TYPE to Type.serialize(type),
|
||||
DIRECTION to Direction.serialize(direction),
|
||||
TIMESTAMP to timestamp,
|
||||
RINGER to ringer
|
||||
)
|
||||
.run()
|
||||
}
|
||||
}
|
||||
|
||||
fun insertOrUpdateGroupCallFromExternalEvent(
|
||||
groupRecipientId: RecipientId,
|
||||
sender: RecipientId,
|
||||
timestamp: Long,
|
||||
messageGroupCallEraId: String?
|
||||
) {
|
||||
insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId,
|
||||
sender,
|
||||
timestamp,
|
||||
messageGroupCallEraId,
|
||||
emptyList(),
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
fun insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId: RecipientId,
|
||||
sender: RecipientId,
|
||||
timestamp: Long,
|
||||
peekGroupCallEraId: String?,
|
||||
peekJoinedUuids: Collection<UUID>,
|
||||
isCallFull: Boolean
|
||||
) {
|
||||
writableDatabase.withinTransaction {
|
||||
if (peekGroupCallEraId.isNullOrEmpty()) {
|
||||
Log.w(TAG, "Dropping local call event with null era id.")
|
||||
return@withinTransaction
|
||||
}
|
||||
|
||||
val callId = CallId.fromEra(peekGroupCallEraId).longValue()
|
||||
val call = getCallById(callId)
|
||||
val messageId: MessageId = if (call != null) {
|
||||
if (call.event == Event.DELETE) {
|
||||
Log.d(TAG, "Dropping group call update for deleted call.")
|
||||
return@withinTransaction
|
||||
}
|
||||
|
||||
if (call.type != Type.GROUP_CALL) {
|
||||
Log.d(TAG, "Dropping unsupported update message for non-group-call call.")
|
||||
return@withinTransaction
|
||||
}
|
||||
|
||||
if (call.messageId == null) {
|
||||
Log.d(TAG, "Dropping group call update for call without an attached message.")
|
||||
return@withinTransaction
|
||||
}
|
||||
|
||||
SignalDatabase.messages.updateGroupCall(
|
||||
call.messageId,
|
||||
peekGroupCallEraId,
|
||||
peekJoinedUuids,
|
||||
isCallFull
|
||||
)
|
||||
} else {
|
||||
SignalDatabase.messages.insertGroupCall(
|
||||
groupRecipientId,
|
||||
sender,
|
||||
timestamp,
|
||||
peekGroupCallEraId,
|
||||
peekJoinedUuids,
|
||||
isCallFull
|
||||
)
|
||||
}
|
||||
|
||||
insertCallEventFromGroupUpdate(
|
||||
callId,
|
||||
messageId,
|
||||
sender,
|
||||
groupRecipientId,
|
||||
timestamp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertCallEventFromGroupUpdate(
|
||||
callId: Long,
|
||||
messageId: MessageId?,
|
||||
sender: RecipientId,
|
||||
groupRecipientId: RecipientId,
|
||||
timestamp: Long
|
||||
) {
|
||||
if (messageId != null) {
|
||||
val call = getCallById(callId)
|
||||
if (call == null) {
|
||||
val direction = if (sender == Recipient.self().id) Direction.OUTGOING else Direction.INCOMING
|
||||
|
||||
writableDatabase
|
||||
.insertInto(TABLE_NAME)
|
||||
.values(
|
||||
CALL_ID to callId,
|
||||
MESSAGE_ID to messageId.id,
|
||||
PEER to groupRecipientId.toLong(),
|
||||
EVENT to Event.serialize(Event.GENERIC_GROUP_CALL),
|
||||
TYPE to Type.serialize(Type.GROUP_CALL),
|
||||
DIRECTION to Direction.serialize(direction),
|
||||
TIMESTAMP to timestamp,
|
||||
RINGER to null
|
||||
)
|
||||
.run()
|
||||
|
||||
Log.d(TAG, "Inserted new call event from group call update message. Call Id: $callId")
|
||||
} else {
|
||||
if (timestamp < call.timestamp) {
|
||||
setTimestamp(callId, timestamp)
|
||||
Log.d(TAG, "Updated call event timestamp for call id $callId")
|
||||
}
|
||||
|
||||
if (call.messageId == null) {
|
||||
setMessageId(callId, messageId)
|
||||
Log.d(TAG, "Updated call event message id for newly inserted group call state: $callId")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Skipping call event processing for null era id.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Since this does not alter the call table, we can simply pass this directly through to the old handler.
|
||||
*/
|
||||
fun updateGroupCallFromPeek(
|
||||
threadId: Long,
|
||||
peekGroupCallEraId: String?,
|
||||
peekJoinedUuids: Collection<UUID>,
|
||||
isCallFull: Boolean
|
||||
): Boolean {
|
||||
return SignalDatabase.messages.updatePreviousGroupCall(threadId, peekGroupCallEraId, peekJoinedUuids, isCallFull)
|
||||
}
|
||||
|
||||
fun insertOrUpdateGroupCallFromRingState(
|
||||
ringId: Long,
|
||||
groupRecipientId: RecipientId,
|
||||
ringerRecipient: RecipientId,
|
||||
dateReceived: Long,
|
||||
ringState: RingUpdate
|
||||
) {
|
||||
handleGroupRingState(ringId, groupRecipientId, ringerRecipient, dateReceived, ringState)
|
||||
}
|
||||
|
||||
fun insertOrUpdateGroupCallFromRingState(
|
||||
ringId: Long,
|
||||
groupRecipientId: RecipientId,
|
||||
ringerUUID: UUID,
|
||||
dateReceived: Long,
|
||||
ringState: RingUpdate
|
||||
) {
|
||||
val ringerRecipient = Recipient.externalPush(ServiceId.from(ringerUUID))
|
||||
handleGroupRingState(ringId, groupRecipientId, ringerRecipient.id, dateReceived, ringState)
|
||||
}
|
||||
|
||||
fun isRingCancelled(ringId: Long): Boolean {
|
||||
val call = getCallById(ringId) ?: return false
|
||||
return call.event != Event.RINGING
|
||||
}
|
||||
|
||||
private fun handleGroupRingState(
|
||||
ringId: Long,
|
||||
groupRecipientId: RecipientId,
|
||||
ringerRecipient: RecipientId,
|
||||
dateReceived: Long,
|
||||
ringState: RingUpdate
|
||||
) {
|
||||
val call = getCallById(ringId)
|
||||
if (call != null) {
|
||||
if (call.event == Event.DELETE) {
|
||||
Log.d(TAG, "Ignoring ring request for $ringId since its event has been deleted.")
|
||||
return
|
||||
}
|
||||
|
||||
when (ringState) {
|
||||
RingUpdate.REQUESTED -> {
|
||||
when (call.event) {
|
||||
Event.GENERIC_GROUP_CALL -> updateEventFromRingState(ringId, Event.RINGING, ringerRecipient)
|
||||
Event.JOINED -> updateEventFromRingState(ringId, Event.ACCEPTED, ringerRecipient)
|
||||
else -> Log.w(TAG, "Received a REQUESTED ring event while in ${call.event}. Ignoring.")
|
||||
}
|
||||
}
|
||||
RingUpdate.EXPIRED_REQUEST, RingUpdate.CANCELLED_BY_RINGER -> {
|
||||
when (call.event) {
|
||||
Event.GENERIC_GROUP_CALL, Event.RINGING -> updateEventFromRingState(ringId, Event.MISSED, ringerRecipient)
|
||||
Event.OUTGOING_RING -> Log.w(TAG, "Received an expiration or cancellation while in OUTGOING_RING state. Ignoring.")
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
RingUpdate.BUSY_LOCALLY, RingUpdate.BUSY_ON_ANOTHER_DEVICE -> {
|
||||
when (call.event) {
|
||||
Event.JOINED -> updateEventFromRingState(ringId, Event.ACCEPTED)
|
||||
Event.GENERIC_GROUP_CALL, Event.RINGING -> updateEventFromRingState(ringId, Event.MISSED)
|
||||
else -> Log.w(TAG, "Received a busy event we can't process. Ignoring.")
|
||||
}
|
||||
}
|
||||
RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE -> {
|
||||
updateEventFromRingState(ringId, Event.ACCEPTED)
|
||||
}
|
||||
RingUpdate.DECLINED_ON_ANOTHER_DEVICE -> {
|
||||
when (call.event) {
|
||||
Event.RINGING, Event.MISSED -> updateEventFromRingState(ringId, Event.DECLINED)
|
||||
Event.OUTGOING_RING -> Log.w(TAG, "Received DECLINED_ON_ANOTHER_DEVICE while in OUTGOING_RING state.")
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val event: Event = when (ringState) {
|
||||
RingUpdate.REQUESTED -> Event.RINGING
|
||||
RingUpdate.EXPIRED_REQUEST -> Event.MISSED
|
||||
RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE -> {
|
||||
Log.w(TAG, "Missed original ring request for $ringId")
|
||||
Event.ACCEPTED
|
||||
}
|
||||
RingUpdate.DECLINED_ON_ANOTHER_DEVICE -> {
|
||||
Log.w(TAG, "Missed original ring request for $ringId")
|
||||
Event.DECLINED
|
||||
}
|
||||
RingUpdate.BUSY_LOCALLY, RingUpdate.BUSY_ON_ANOTHER_DEVICE -> {
|
||||
Log.w(TAG, "Missed original ring request for $ringId")
|
||||
Event.MISSED
|
||||
}
|
||||
RingUpdate.CANCELLED_BY_RINGER -> {
|
||||
Log.w(TAG, "Missed original ring request for $ringId")
|
||||
Event.MISSED
|
||||
}
|
||||
}
|
||||
|
||||
createEventFromRingState(ringId, groupRecipientId, ringerRecipient, event, dateReceived)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateEventFromRingState(
|
||||
callId: Long,
|
||||
event: Event,
|
||||
ringerRecipient: RecipientId
|
||||
) {
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(
|
||||
EVENT to Event.serialize(event),
|
||||
RINGER to ringerRecipient.serialize()
|
||||
)
|
||||
.where("$CALL_ID = ?", callId)
|
||||
.run()
|
||||
|
||||
Log.d(TAG, "Updated ring state to $event")
|
||||
}
|
||||
|
||||
private fun updateEventFromRingState(
|
||||
callId: Long,
|
||||
event: Event
|
||||
) {
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(
|
||||
EVENT to Event.serialize(event)
|
||||
)
|
||||
.where("$CALL_ID = ?", callId)
|
||||
.run()
|
||||
|
||||
Log.d(TAG, "Updated ring state to $event")
|
||||
}
|
||||
|
||||
private fun createEventFromRingState(
|
||||
callId: Long,
|
||||
groupRecipientId: RecipientId,
|
||||
ringerRecipient: RecipientId,
|
||||
event: Event,
|
||||
timestamp: Long
|
||||
) {
|
||||
val direction = if (ringerRecipient == Recipient.self().id) Direction.OUTGOING else Direction.INCOMING
|
||||
|
||||
writableDatabase.withinTransaction { db ->
|
||||
val messageId = SignalDatabase.messages.insertGroupCall(
|
||||
groupRecipientId = groupRecipientId,
|
||||
sender = ringerRecipient,
|
||||
timestamp = timestamp,
|
||||
eraId = "",
|
||||
joinedUuids = emptyList(),
|
||||
isCallFull = false
|
||||
)
|
||||
|
||||
db
|
||||
.insertInto(TABLE_NAME)
|
||||
.values(
|
||||
CALL_ID to callId,
|
||||
MESSAGE_ID to messageId.id,
|
||||
PEER to groupRecipientId.toLong(),
|
||||
EVENT to Event.serialize(event),
|
||||
TYPE to Type.serialize(Type.GROUP_CALL),
|
||||
DIRECTION to Direction.serialize(direction),
|
||||
TIMESTAMP to timestamp,
|
||||
RINGER to ringerRecipient.toLong()
|
||||
)
|
||||
.run()
|
||||
}
|
||||
|
||||
Log.d(TAG, "Inserted a new call event for $callId with event $event")
|
||||
}
|
||||
|
||||
fun setTimestamp(callId: Long, timestamp: Long) {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
val call = getCallById(callId)
|
||||
if (call == null || call.event == Event.DELETE) {
|
||||
Log.d(TAG, "Refusing to update deleted call event.")
|
||||
return@withinTransaction
|
||||
}
|
||||
|
||||
db
|
||||
.update(TABLE_NAME)
|
||||
.values(TIMESTAMP to timestamp)
|
||||
.where("$CALL_ID = ?", callId)
|
||||
.run()
|
||||
|
||||
if (call.messageId != null) {
|
||||
SignalDatabase.messages.updateCallTimestamps(call.messageId, timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setMessageId(callId: Long, messageId: MessageId) {
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(MESSAGE_ID to messageId.id)
|
||||
.where("$CALL_ID = ?", callId)
|
||||
.run()
|
||||
}
|
||||
|
||||
fun deleteCallEvents(callIds: Set<Long>) {
|
||||
val messageIds = getMessageIds(callIds)
|
||||
SignalDatabase.messages.deleteCallUpdates(messageIds)
|
||||
updateCallEventDeletionTimestamps()
|
||||
}
|
||||
|
||||
fun deleteAllCallEventsExcept(callIds: Set<Long>) {
|
||||
val messageIds = getMessageIds(callIds)
|
||||
SignalDatabase.messages.deleteAllCallUpdatesExcept(messageIds)
|
||||
updateCallEventDeletionTimestamps()
|
||||
}
|
||||
|
||||
@Discouraged("Using this method is generally considered an error. Utilize other deletion methods instead of this.")
|
||||
fun deleteAllCalls() {
|
||||
Log.w(TAG, "Deleting all calls from the local database.")
|
||||
writableDatabase
|
||||
.delete(TABLE_NAME)
|
||||
.run()
|
||||
}
|
||||
|
||||
private fun getMessageIds(callIds: Set<Long>): Set<Long> {
|
||||
val queries = SqlUtil.buildCollectionQuery(
|
||||
CALL_ID,
|
||||
callIds,
|
||||
"$MESSAGE_ID NOT NULL AND"
|
||||
)
|
||||
|
||||
return queries.map { query ->
|
||||
readableDatabase.select(MESSAGE_ID).from(TABLE_NAME).where(query.where, query.whereArgs).run().readToList {
|
||||
it.requireLong(MESSAGE_ID)
|
||||
}
|
||||
}.flatten().toSet()
|
||||
}
|
||||
|
||||
private fun checkIsGroupOrAdHocCall(call: Call) {
|
||||
check(call.type == Type.GROUP_CALL || call.type == Type.AD_HOC_CALL)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
private fun getCallsCursor(isCount: Boolean, offset: Int, limit: Int, searchTerm: String?, filter: CallLogFilter): Cursor {
|
||||
val filterClause = when (filter) {
|
||||
CallLogFilter.ALL -> SqlUtil.buildQuery("")
|
||||
CallLogFilter.ALL -> SqlUtil.buildQuery("$EVENT != ${Event.serialize(Event.DELETE)}")
|
||||
CallLogFilter.MISSED -> SqlUtil.buildQuery("$EVENT == ${Event.serialize(Event.MISSED)}")
|
||||
}
|
||||
|
||||
|
@ -233,12 +778,22 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
val type: Type,
|
||||
val direction: Direction,
|
||||
val event: Event,
|
||||
val messageId: Long
|
||||
val messageId: Long?,
|
||||
val timestamp: Long,
|
||||
val ringerRecipient: RecipientId?
|
||||
) {
|
||||
val messageType: Long = getMessageType(type, direction, event)
|
||||
|
||||
companion object Deserializer : Serializer<Call, Cursor> {
|
||||
fun getMessageType(type: Type, direction: Direction, event: Event): Long {
|
||||
if (type == Type.GROUP_CALL) {
|
||||
return MessageTypes.GROUP_CALL_TYPE
|
||||
}
|
||||
|
||||
if (type == Type.AD_HOC_CALL) {
|
||||
error("Ad-Hoc calls are not linked to messages.")
|
||||
}
|
||||
|
||||
return if (direction == Direction.INCOMING && event == Event.MISSED) {
|
||||
if (type == Type.VIDEO_CALL) MessageTypes.MISSED_VIDEO_CALL_TYPE else MessageTypes.MISSED_AUDIO_CALL_TYPE
|
||||
} else if (direction == Direction.INCOMING) {
|
||||
|
@ -259,7 +814,15 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
type = data.requireObject(TYPE, Type.Serializer),
|
||||
direction = data.requireObject(DIRECTION, Direction.Serializer),
|
||||
event = data.requireObject(EVENT, Event.Serializer),
|
||||
messageId = data.requireLong(MESSAGE_ID)
|
||||
messageId = data.requireLong(MESSAGE_ID).takeIf { it > 0L },
|
||||
timestamp = data.requireLong(TIMESTAMP),
|
||||
ringerRecipient = data.requireLong(RINGER).let {
|
||||
if (it > 0) {
|
||||
RecipientId.from(it)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -267,7 +830,9 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
|
||||
enum class Type(private val code: Int) {
|
||||
AUDIO_CALL(0),
|
||||
VIDEO_CALL(1);
|
||||
VIDEO_CALL(1),
|
||||
GROUP_CALL(3),
|
||||
AD_HOC_CALL(4);
|
||||
|
||||
companion object Serializer : IntSerializer<Type> {
|
||||
override fun serialize(data: Type): Int = data.code
|
||||
|
@ -276,6 +841,8 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
return when (data) {
|
||||
AUDIO_CALL.code -> AUDIO_CALL
|
||||
VIDEO_CALL.code -> VIDEO_CALL
|
||||
GROUP_CALL.code -> GROUP_CALL
|
||||
AD_HOC_CALL.code -> AD_HOC_CALL
|
||||
else -> throw IllegalArgumentException("Unknown type $data")
|
||||
}
|
||||
}
|
||||
|
@ -286,6 +853,8 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
CallEvent.Type.UNKNOWN_TYPE -> null
|
||||
CallEvent.Type.AUDIO_CALL -> AUDIO_CALL
|
||||
CallEvent.Type.VIDEO_CALL -> VIDEO_CALL
|
||||
CallEvent.Type.GROUP_CALL -> GROUP_CALL
|
||||
CallEvent.Type.AD_HOC_CALL -> AD_HOC_CALL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -318,22 +887,69 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
}
|
||||
|
||||
enum class Event(private val code: Int) {
|
||||
/**
|
||||
* 1:1 Calls only.
|
||||
*/
|
||||
ONGOING(0),
|
||||
|
||||
/**
|
||||
* 1:1 and Group Calls.
|
||||
*
|
||||
* Group calls: You accepted a ring.
|
||||
*/
|
||||
ACCEPTED(1),
|
||||
|
||||
/**
|
||||
* 1:1 Calls only.
|
||||
*/
|
||||
NOT_ACCEPTED(2),
|
||||
MISSED(3);
|
||||
|
||||
/**
|
||||
* 1:1 and Group/Ad-Hoc Calls.
|
||||
*
|
||||
* Group calls: The remote ring has expired or was cancelled by the ringer.
|
||||
*/
|
||||
MISSED(3),
|
||||
|
||||
/**
|
||||
* 1:1 and Group/Ad-Hoc Calls.
|
||||
*/
|
||||
DELETE(4),
|
||||
|
||||
/**
|
||||
* Group/Ad-Hoc Calls only.
|
||||
*
|
||||
* Initial state.
|
||||
*/
|
||||
GENERIC_GROUP_CALL(5),
|
||||
|
||||
/**
|
||||
* Group Calls: User has joined the group call.
|
||||
*/
|
||||
JOINED(6),
|
||||
|
||||
/**
|
||||
* Group Calls: If a ring was requested by another user.
|
||||
*/
|
||||
RINGING(7),
|
||||
|
||||
/**
|
||||
* Group Calls: If you declined a ring.
|
||||
*/
|
||||
DECLINED(8),
|
||||
|
||||
/**
|
||||
* Group Calls: If you are ringing a group.
|
||||
*/
|
||||
OUTGOING_RING(9);
|
||||
|
||||
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")
|
||||
}
|
||||
return values().firstOrNull {
|
||||
it.code == data
|
||||
} ?: throw IllegalArgumentException("Unknown event $data")
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
@ -342,6 +958,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
CallEvent.Event.UNKNOWN_ACTION -> null
|
||||
CallEvent.Event.ACCEPTED -> ACCEPTED
|
||||
CallEvent.Event.NOT_ACCEPTED -> NOT_ACCEPTED
|
||||
CallEvent.Event.DELETE -> DELETE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import org.signal.core.util.CursorUtil
|
||||
import org.signal.core.util.SqlUtil
|
||||
import org.signal.ringrtc.CallManager
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Track state of Group Call ring cancellations.
|
||||
*/
|
||||
class GroupCallRingTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper) {
|
||||
|
||||
companion object {
|
||||
private val VALID_RING_DURATION = TimeUnit.MINUTES.toMillis(30)
|
||||
|
||||
private const val TABLE_NAME = "group_call_ring"
|
||||
|
||||
private const val ID = "_id"
|
||||
private const val RING_ID = "ring_id"
|
||||
private const val DATE_RECEIVED = "date_received"
|
||||
private const val RING_STATE = "ring_state"
|
||||
|
||||
@JvmField
|
||||
val CREATE_TABLE = """
|
||||
CREATE TABLE $TABLE_NAME (
|
||||
$ID INTEGER PRIMARY KEY,
|
||||
$RING_ID INTEGER UNIQUE,
|
||||
$DATE_RECEIVED INTEGER,
|
||||
$RING_STATE INTEGER
|
||||
)
|
||||
""".trimIndent()
|
||||
|
||||
@JvmField
|
||||
val CREATE_INDEXES = arrayOf(
|
||||
"CREATE INDEX date_received_index on $TABLE_NAME ($DATE_RECEIVED)"
|
||||
)
|
||||
}
|
||||
|
||||
fun isCancelled(ringId: Long): Boolean {
|
||||
val db = databaseHelper.signalReadableDatabase
|
||||
|
||||
db.query(TABLE_NAME, null, "$RING_ID = ?", SqlUtil.buildArgs(ringId), null, null, null).use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
return CursorUtil.requireInt(cursor, RING_STATE) != 0
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun insertGroupRing(ringId: Long, dateReceived: Long, ringState: CallManager.RingUpdate) {
|
||||
val db = databaseHelper.signalWritableDatabase
|
||||
val values = ContentValues().apply {
|
||||
put(RING_ID, ringId)
|
||||
put(DATE_RECEIVED, dateReceived)
|
||||
put(RING_STATE, ringState.toCode())
|
||||
}
|
||||
db.insert(TABLE_NAME, null, values)
|
||||
|
||||
removeOldRings()
|
||||
}
|
||||
|
||||
fun insertOrUpdateGroupRing(ringId: Long, dateReceived: Long, ringState: CallManager.RingUpdate) {
|
||||
val db = databaseHelper.signalWritableDatabase
|
||||
val values = ContentValues().apply {
|
||||
put(RING_ID, ringId)
|
||||
put(DATE_RECEIVED, dateReceived)
|
||||
put(RING_STATE, ringState.toCode())
|
||||
}
|
||||
db.insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE)
|
||||
|
||||
removeOldRings()
|
||||
}
|
||||
|
||||
fun removeOldRings() {
|
||||
val db = databaseHelper.signalWritableDatabase
|
||||
|
||||
db.delete(TABLE_NAME, "$DATE_RECEIVED < ?", SqlUtil.buildArgs(System.currentTimeMillis() - VALID_RING_DURATION))
|
||||
}
|
||||
|
||||
fun deleteAll() {
|
||||
databaseHelper.signalWritableDatabase.delete(TABLE_NAME, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun CallManager.RingUpdate.toCode(): Int {
|
||||
return when (this) {
|
||||
CallManager.RingUpdate.REQUESTED -> 0
|
||||
CallManager.RingUpdate.EXPIRED_REQUEST -> 1
|
||||
CallManager.RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE -> 2
|
||||
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE -> 3
|
||||
CallManager.RingUpdate.BUSY_LOCALLY -> 4
|
||||
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE -> 5
|
||||
CallManager.RingUpdate.CANCELLED_BY_RINGER -> 6
|
||||
}
|
||||
}
|
|
@ -38,7 +38,6 @@ import org.signal.core.util.SqlUtil.buildSingleCollectionQuery
|
|||
import org.signal.core.util.SqlUtil.buildTrueUpdateQuery
|
||||
import org.signal.core.util.SqlUtil.getNextAutoIncrementId
|
||||
import org.signal.core.util.delete
|
||||
import org.signal.core.util.emptyIfNull
|
||||
import org.signal.core.util.exists
|
||||
import org.signal.core.util.forEach
|
||||
import org.signal.core.util.insertInto
|
||||
|
@ -71,6 +70,7 @@ import org.thoughtcrime.securesms.conversation.MessageStyler
|
|||
import org.thoughtcrime.securesms.database.EarlyReceiptCache.Receipt
|
||||
import org.thoughtcrime.securesms.database.MentionUtil.UpdatedBodyAndMentions
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.attachments
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.calls
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.distributionLists
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.groupReceipts
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.groups
|
||||
|
@ -400,6 +400,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
|||
($TYPE = ${MessageTypes.MISSED_AUDIO_CALL_TYPE})
|
||||
OR
|
||||
($TYPE = ${MessageTypes.MISSED_VIDEO_CALL_TYPE})
|
||||
OR
|
||||
($TYPE = ${MessageTypes.GROUP_CALL_TYPE})
|
||||
)""".toSingleLine()
|
||||
|
||||
@JvmStatic
|
||||
|
@ -802,27 +804,24 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
|||
ApplicationDependencies.getDatabaseObserver().notifyMessageUpdateObservers(MessageId(messageId))
|
||||
}
|
||||
|
||||
fun insertOrUpdateGroupCall(
|
||||
fun insertGroupCall(
|
||||
groupRecipientId: RecipientId,
|
||||
sender: RecipientId,
|
||||
timestamp: Long,
|
||||
peekGroupCallEraId: String?,
|
||||
peekJoinedUuids: Collection<UUID>,
|
||||
eraId: String,
|
||||
joinedUuids: Collection<UUID>,
|
||||
isCallFull: Boolean
|
||||
) {
|
||||
): MessageId {
|
||||
val recipient = Recipient.resolved(groupRecipientId)
|
||||
val threadId = threads.getOrCreateThreadIdFor(recipient)
|
||||
val peerEraIdSameAsPrevious = updatePreviousGroupCall(threadId, peekGroupCallEraId, peekJoinedUuids, isCallFull)
|
||||
|
||||
writableDatabase.withinTransaction { db ->
|
||||
if (!peerEraIdSameAsPrevious && !Util.isEmpty(peekGroupCallEraId)) {
|
||||
val messageId: MessageId = writableDatabase.withinTransaction { db ->
|
||||
val self = Recipient.self()
|
||||
val markRead = peekJoinedUuids.contains(self.requireServiceId().uuid()) || self.id == sender
|
||||
val updateDetails = GroupCallUpdateDetails.newBuilder()
|
||||
.setEraId(peekGroupCallEraId.emptyIfNull())
|
||||
val markRead = joinedUuids.contains(self.requireServiceId().uuid()) || self.id == sender
|
||||
val updateDetails: ByteArray = GroupCallUpdateDetails.newBuilder()
|
||||
.setEraId(eraId)
|
||||
.setStartedCallUuid(Recipient.resolved(sender).requireServiceId().toString())
|
||||
.setStartedCallTimestamp(timestamp)
|
||||
.addAllInCallUuids(peekJoinedUuids.map { it.toString() }.toList())
|
||||
.addAllInCallUuids(joinedUuids.map { it.toString() })
|
||||
.setIsCallFull(isCallFull)
|
||||
.build()
|
||||
.toByteArray()
|
||||
|
@ -838,86 +837,78 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
|||
THREAD_ID to threadId
|
||||
)
|
||||
|
||||
db.insert(TABLE_NAME, null, values)
|
||||
|
||||
val messageId = MessageId(db.insert(TABLE_NAME, null, values))
|
||||
threads.incrementUnread(threadId, 1, 0)
|
||||
}
|
||||
|
||||
threads.update(threadId, true)
|
||||
|
||||
messageId
|
||||
}
|
||||
|
||||
notifyConversationListeners(threadId)
|
||||
TrimThreadJob.enqueueAsync(threadId)
|
||||
|
||||
return messageId
|
||||
}
|
||||
|
||||
fun insertOrUpdateGroupCall(
|
||||
groupRecipientId: RecipientId,
|
||||
sender: RecipientId,
|
||||
timestamp: Long,
|
||||
messageGroupCallEraId: String?
|
||||
) {
|
||||
val threadId = writableDatabase.withinTransaction { db ->
|
||||
val recipient = Recipient.resolved(groupRecipientId)
|
||||
val threadId = threads.getOrCreateThreadIdFor(recipient)
|
||||
|
||||
val cursor = db
|
||||
.select(*MMS_PROJECTION)
|
||||
.from(TABLE_NAME)
|
||||
.where("$TYPE = ? AND $THREAD_ID = ?", MessageTypes.GROUP_CALL_TYPE, threadId)
|
||||
.orderBy("$DATE_RECEIVED DESC")
|
||||
.limit(1)
|
||||
.run()
|
||||
|
||||
var sameEraId = false
|
||||
|
||||
MmsReader(cursor).use { reader ->
|
||||
val record: MessageRecord? = reader.firstOrNull()
|
||||
|
||||
if (record != null) {
|
||||
val groupCallUpdateDetails = GroupCallUpdateDetailsUtil.parse(record.body)
|
||||
sameEraId = groupCallUpdateDetails.eraId == messageGroupCallEraId && !Util.isEmpty(messageGroupCallEraId)
|
||||
|
||||
if (!sameEraId) {
|
||||
db.update(TABLE_NAME)
|
||||
.values(BODY to GroupCallUpdateDetailsUtil.createUpdatedBody(groupCallUpdateDetails, emptyList(), false))
|
||||
.where("$ID = ?", record.id)
|
||||
.run()
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Updates the timestamps associated with the given message id to the given ts
|
||||
*/
|
||||
fun updateCallTimestamps(messageId: Long, timestamp: Long) {
|
||||
val message = try {
|
||||
getMessageRecord(messageId = messageId)
|
||||
} catch (e: NoSuchMessageException) {
|
||||
error("Message $messageId does not exist")
|
||||
}
|
||||
|
||||
if (!sameEraId && !Util.isEmpty(messageGroupCallEraId)) {
|
||||
val updateDetails = GroupCallUpdateDetails.newBuilder()
|
||||
.setEraId(Util.emptyIfNull(messageGroupCallEraId))
|
||||
.setStartedCallUuid(Recipient.resolved(sender).requireServiceId().toString())
|
||||
.setStartedCallTimestamp(timestamp)
|
||||
.addAllInCallUuids(emptyList())
|
||||
.setIsCallFull(false)
|
||||
.build()
|
||||
.toByteArray()
|
||||
|
||||
val values = contentValuesOf(
|
||||
RECIPIENT_ID to sender.serialize(),
|
||||
RECIPIENT_DEVICE_ID to 1,
|
||||
DATE_RECEIVED to timestamp,
|
||||
val updateDetail = GroupCallUpdateDetailsUtil.parse(message.body)
|
||||
val contentValues = contentValuesOf(
|
||||
BODY to Base64.encodeBytes(updateDetail.toBuilder().setStartedCallTimestamp(timestamp).build().toByteArray()),
|
||||
DATE_SENT to timestamp,
|
||||
READ to 0,
|
||||
BODY to Base64.encodeBytes(updateDetails),
|
||||
TYPE to MessageTypes.GROUP_CALL_TYPE,
|
||||
THREAD_ID to threadId
|
||||
DATE_RECEIVED to timestamp
|
||||
)
|
||||
|
||||
db.insert(TABLE_NAME, null, values)
|
||||
threads.incrementUnread(threadId, 1, 0)
|
||||
val query = buildTrueUpdateQuery(ID_WHERE, buildArgs(messageId), contentValues)
|
||||
val updated = writableDatabase.update(TABLE_NAME, contentValues, query.where, query.whereArgs) > 0
|
||||
|
||||
if (updated) {
|
||||
notifyConversationListeners(message.threadId)
|
||||
}
|
||||
}
|
||||
|
||||
threads.update(threadId, true)
|
||||
|
||||
threadId
|
||||
fun updateGroupCall(
|
||||
messageId: Long,
|
||||
eraId: String,
|
||||
joinedUuids: Collection<UUID>,
|
||||
isCallFull: Boolean
|
||||
): MessageId {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
val message = try {
|
||||
getMessageRecord(messageId = messageId)
|
||||
} catch (e: NoSuchMessageException) {
|
||||
error("Message $messageId does not exist.")
|
||||
}
|
||||
|
||||
notifyConversationListeners(threadId)
|
||||
TrimThreadJob.enqueueAsync(threadId)
|
||||
val updateDetail = GroupCallUpdateDetailsUtil.parse(message.body)
|
||||
val containsSelf = joinedUuids.contains(SignalStore.account().requireAci().uuid())
|
||||
val sameEraId = updateDetail.eraId == eraId && !Util.isEmpty(eraId)
|
||||
val inCallUuids = if (sameEraId) joinedUuids.map { it.toString() } else emptyList()
|
||||
val contentValues = contentValuesOf(
|
||||
BODY to GroupCallUpdateDetailsUtil.createUpdatedBody(updateDetail, inCallUuids, isCallFull)
|
||||
)
|
||||
|
||||
if (sameEraId && containsSelf) {
|
||||
contentValues.put(READ, 1)
|
||||
}
|
||||
|
||||
val query = buildTrueUpdateQuery(ID_WHERE, buildArgs(messageId), contentValues)
|
||||
val updated = db.update(TABLE_NAME, contentValues, query.where, query.whereArgs) > 0
|
||||
|
||||
if (updated) {
|
||||
notifyConversationListeners(message.threadId)
|
||||
}
|
||||
}
|
||||
|
||||
return MessageId(messageId)
|
||||
}
|
||||
|
||||
fun updatePreviousGroupCall(threadId: Long, peekGroupCallEraId: String?, peekJoinedUuids: Collection<UUID>, isCallFull: Boolean): Boolean {
|
||||
|
@ -3099,6 +3090,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
|||
.where("$ID = ?", messageId)
|
||||
.run()
|
||||
|
||||
calls.updateCallEventDeletionTimestamps()
|
||||
threads.setLastScrolled(threadId, 0)
|
||||
val threadDeleted = threads.update(threadId, false)
|
||||
|
||||
|
@ -3356,6 +3348,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
|||
|
||||
if (deletes > 0) {
|
||||
Log.i(TAG, "Deleted $deletes abandoned messages")
|
||||
calls.updateCallEventDeletionTimestamps()
|
||||
}
|
||||
|
||||
return deletes
|
||||
|
@ -3385,6 +3378,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
|||
groupReceipts.deleteAllRows()
|
||||
mentions.deleteAllMentions()
|
||||
writableDatabase.delete(TABLE_NAME).run()
|
||||
calls.updateCallEventDeletionTimestamps()
|
||||
|
||||
OptimizeMessageSearchIndexJob.enqueue()
|
||||
}
|
||||
|
|
|
@ -65,7 +65,6 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
|
|||
val emojiSearchTable: EmojiSearchTable = EmojiSearchTable(context, this)
|
||||
val messageSendLogTables: MessageSendLogTables = MessageSendLogTables(context, this)
|
||||
val avatarPickerDatabase: AvatarPickerDatabase = AvatarPickerDatabase(context, this)
|
||||
val groupCallRingTable: GroupCallRingTable = GroupCallRingTable(context, this)
|
||||
val reactionTable: ReactionTable = ReactionTable(context, this)
|
||||
val notificationProfileDatabase: NotificationProfileDatabase = NotificationProfileDatabase(context, this)
|
||||
val donationReceiptTable: DonationReceiptTable = DonationReceiptTable(context, this)
|
||||
|
@ -103,7 +102,6 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
|
|||
db.execSQL(ChatColorsTable.CREATE_TABLE)
|
||||
db.execSQL(EmojiSearchTable.CREATE_TABLE)
|
||||
db.execSQL(AvatarPickerDatabase.CREATE_TABLE)
|
||||
db.execSQL(GroupCallRingTable.CREATE_TABLE)
|
||||
db.execSQL(ReactionTable.CREATE_TABLE)
|
||||
db.execSQL(DonationReceiptTable.CREATE_TABLE)
|
||||
db.execSQL(StorySendTable.CREATE_TABLE)
|
||||
|
@ -129,7 +127,6 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
|
|||
executeStatements(db, MentionTable.CREATE_INDEXES)
|
||||
executeStatements(db, PaymentTable.CREATE_INDEXES)
|
||||
executeStatements(db, MessageSendLogTables.CREATE_INDEXES)
|
||||
executeStatements(db, GroupCallRingTable.CREATE_INDEXES)
|
||||
executeStatements(db, NotificationProfileDatabase.CREATE_INDEXES)
|
||||
executeStatements(db, DonationReceiptTable.CREATE_INDEXS)
|
||||
executeStatements(db, StorySendTable.CREATE_INDEXS)
|
||||
|
@ -389,11 +386,6 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
|
|||
val emojiSearch: EmojiSearchTable
|
||||
get() = instance!!.emojiSearchTable
|
||||
|
||||
@get:JvmStatic
|
||||
@get:JvmName("groupCallRings")
|
||||
val groupCallRings: GroupCallRingTable
|
||||
get() = instance!!.groupCallRingTable
|
||||
|
||||
@get:JvmStatic
|
||||
@get:JvmName("groupReceipts")
|
||||
val groupReceipts: GroupReceiptTable
|
||||
|
|
|
@ -31,8 +31,8 @@ import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
|||
import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter
|
||||
import org.thoughtcrime.securesms.database.MessageTable.MarkedMessageInfo
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.attachments
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.calls
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.drafts
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.groupCallRings
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.groupReceipts
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.mentions
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.messageLog
|
||||
|
@ -380,6 +380,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
|||
setLastScrolled(threadId, 0)
|
||||
update(threadId, false)
|
||||
notifyConversationListeners(threadId)
|
||||
SignalDatabase.calls.updateCallEventDeletionTimestamps()
|
||||
} else {
|
||||
Log.i(TAG, "Trimming deleted no messages thread: $threadId")
|
||||
}
|
||||
|
@ -1081,13 +1082,14 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
|||
ConversationUtil.clearShortcuts(context, recipientIds)
|
||||
}
|
||||
|
||||
@SuppressLint("DiscouragedApi")
|
||||
fun deleteAllConversations() {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
messageLog.deleteAll()
|
||||
messages.deleteAllThreads()
|
||||
drafts.clearAllDrafts()
|
||||
groupCallRings.deleteAll()
|
||||
db.delete(TABLE_NAME, null, null)
|
||||
calls.deleteAllCalls()
|
||||
}
|
||||
|
||||
notifyConversationListListeners()
|
||||
|
|
|
@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V178_ReportingToken
|
|||
import org.thoughtcrime.securesms.database.helpers.migration.V179_CleanupDanglingMessageSendLogMigration
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V180_RecipientNicknameMigration
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V181_ThreadTableForeignKeyCleanup
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V182_CallTableMigration
|
||||
|
||||
/**
|
||||
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
|
||||
|
@ -45,7 +46,7 @@ object SignalDatabaseMigrations {
|
|||
|
||||
val TAG: String = Log.tag(SignalDatabaseMigrations.javaClass)
|
||||
|
||||
const val DATABASE_VERSION = 181
|
||||
const val DATABASE_VERSION = 182
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
@ -180,6 +181,10 @@ object SignalDatabaseMigrations {
|
|||
if (oldVersion < 181) {
|
||||
V181_ThreadTableForeignKeyCleanup.migrate(context, db, oldVersion, newVersion)
|
||||
}
|
||||
|
||||
if (oldVersion < 182) {
|
||||
V182_CallTableMigration.migrate(context, db, oldVersion, newVersion)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import net.zetetic.database.sqlcipher.SQLiteDatabase
|
||||
import org.thoughtcrime.securesms.database.MessageTable
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
|
||||
/**
|
||||
* Adds a new 'timestamp' column to CallTable and copies in the date_sent column data from
|
||||
* the messages database.
|
||||
*
|
||||
* Adds a new 'ringer' column to the CallTable setting each entry to NULL. This is safe since up
|
||||
* to this point we were not using the table for group calls. This is effectively a replacement for
|
||||
* the GroupCallRing table.
|
||||
*
|
||||
* Removes the 'NOT NULL' condition on message_id and peer, as with ad-hoc calling in place, these
|
||||
* can now be null.
|
||||
*/
|
||||
object V182_CallTableMigration : SignalDatabaseMigration {
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL(
|
||||
"""
|
||||
CREATE TABLE call_tmp (
|
||||
_id INTEGER PRIMARY KEY,
|
||||
call_id INTEGER NOT NULL UNIQUE,
|
||||
message_id INTEGER DEFAULT NULL REFERENCES ${MessageTable.TABLE_NAME} (${MessageTable.ID}) ON DELETE SET NULL,
|
||||
peer INTEGER DEFAULT NULL REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE,
|
||||
type INTEGER NOT NULL,
|
||||
direction INTEGER NOT NULL,
|
||||
event INTEGER NOT NULL,
|
||||
timestamp INTEGER NOT NULL,
|
||||
ringer INTEGER DEFAULT NULL,
|
||||
deletion_timestamp INTEGER DEFAULT 0
|
||||
)
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
db.execSQL(
|
||||
"""
|
||||
INSERT INTO call_tmp
|
||||
SELECT
|
||||
_id,
|
||||
call_id,
|
||||
message_id,
|
||||
peer,
|
||||
type,
|
||||
direction,
|
||||
event,
|
||||
(SELECT date_sent FROM message WHERE message._id = call.message_id) as timestamp,
|
||||
NULL as ringer,
|
||||
0 as deletion_timestamp
|
||||
FROM call
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
db.execSQL("DROP TABLE group_call_ring")
|
||||
db.execSQL("DROP TABLE call")
|
||||
db.execSQL("ALTER TABLE call_tmp RENAME TO call")
|
||||
}
|
||||
}
|
|
@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
|||
import org.thoughtcrime.securesms.push.SignalServiceTrustStore;
|
||||
import org.thoughtcrime.securesms.recipients.LiveRecipientCache;
|
||||
import org.thoughtcrime.securesms.revealable.ViewOnceMessageManager;
|
||||
import org.thoughtcrime.securesms.service.DeletedCallEventManager;
|
||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
import org.thoughtcrime.securesms.service.ExpiringStoriesManager;
|
||||
import org.thoughtcrime.securesms.service.PendingRetryReceiptManager;
|
||||
|
@ -112,6 +113,7 @@ public class ApplicationDependencies {
|
|||
private static volatile ViewOnceMessageManager viewOnceMessageManager;
|
||||
private static volatile ExpiringStoriesManager expiringStoriesManager;
|
||||
private static volatile ExpiringMessageManager expiringMessageManager;
|
||||
private static volatile DeletedCallEventManager deletedCallEventManager;
|
||||
private static volatile Payments payments;
|
||||
private static volatile SignalCallManager signalCallManager;
|
||||
private static volatile ShakeToReport shakeToReport;
|
||||
|
@ -430,6 +432,18 @@ public class ApplicationDependencies {
|
|||
return expiringMessageManager;
|
||||
}
|
||||
|
||||
public static @NonNull DeletedCallEventManager getDeletedCallEventManager() {
|
||||
if (deletedCallEventManager == null) {
|
||||
synchronized (LOCK) {
|
||||
if (deletedCallEventManager == null) {
|
||||
deletedCallEventManager = provider.provideDeletedCallEventManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deletedCallEventManager;
|
||||
}
|
||||
|
||||
public static @NonNull ScheduledMessageManager getScheduledMessageManager() {
|
||||
if (scheduledMessagesManager == null) {
|
||||
synchronized (LOCK) {
|
||||
|
@ -691,6 +705,7 @@ public class ApplicationDependencies {
|
|||
@NonNull ViewOnceMessageManager provideViewOnceMessageManager();
|
||||
@NonNull ExpiringStoriesManager provideExpiringStoriesManager();
|
||||
@NonNull ExpiringMessageManager provideExpiringMessageManager();
|
||||
@NonNull DeletedCallEventManager provideDeletedCallEventManager();
|
||||
@NonNull TypingStatusRepository provideTypingStatusRepository();
|
||||
@NonNull TypingStatusSender provideTypingStatusSender();
|
||||
@NonNull DatabaseObserver provideDatabaseObserver();
|
||||
|
|
|
@ -56,6 +56,7 @@ import org.thoughtcrime.securesms.push.SecurityEventListener;
|
|||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||
import org.thoughtcrime.securesms.recipients.LiveRecipientCache;
|
||||
import org.thoughtcrime.securesms.revealable.ViewOnceMessageManager;
|
||||
import org.thoughtcrime.securesms.service.DeletedCallEventManager;
|
||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
import org.thoughtcrime.securesms.service.ExpiringStoriesManager;
|
||||
import org.thoughtcrime.securesms.service.PendingRetryReceiptManager;
|
||||
|
@ -225,6 +226,11 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
|
|||
return new ExpiringMessageManager(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull DeletedCallEventManager provideDeletedCallEventManager() {
|
||||
return new DeletedCallEventManager(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull ScheduledMessageManager provideScheduledMessageManager() {
|
||||
return new ScheduledMessageManager(context);
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.ringrtc.CallId
|
||||
import org.thoughtcrime.securesms.database.CallTable
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer
|
||||
import org.thoughtcrime.securesms.service.webrtc.CallEventSyncMessageUtil
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Sends a sync event for the given call when the user first joins.
|
||||
*/
|
||||
class CallSyncEventJob private constructor(
|
||||
parameters: Parameters,
|
||||
private val conversationRecipientId: RecipientId,
|
||||
private val callId: Long,
|
||||
private val direction: CallTable.Direction,
|
||||
private val event: CallTable.Event
|
||||
) : BaseJob(parameters) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(CallSyncEventJob::class.java)
|
||||
|
||||
const val KEY = "CallSyncEventJob"
|
||||
|
||||
private const val KEY_CALL_ID = "call_id"
|
||||
private const val KEY_CONVERSATION_ID = "conversation_id"
|
||||
private const val KEY_DIRECTION = "direction"
|
||||
private const val KEY_EVENT = "event"
|
||||
|
||||
@JvmStatic
|
||||
fun createForJoin(conversationRecipientId: RecipientId, callId: Long, isIncoming: Boolean): CallSyncEventJob {
|
||||
return CallSyncEventJob(
|
||||
getParameters(conversationRecipientId),
|
||||
conversationRecipientId,
|
||||
callId,
|
||||
if (isIncoming) CallTable.Direction.INCOMING else CallTable.Direction.OUTGOING,
|
||||
CallTable.Event.ACCEPTED
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun createForDelete(conversationRecipientId: RecipientId, callId: Long, isIncoming: Boolean): CallSyncEventJob {
|
||||
return CallSyncEventJob(
|
||||
getParameters(conversationRecipientId),
|
||||
conversationRecipientId,
|
||||
callId,
|
||||
if (isIncoming) CallTable.Direction.INCOMING else CallTable.Direction.OUTGOING,
|
||||
CallTable.Event.DELETE
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun enqueueDeleteSyncEvents(deletedCalls: Set<CallTable.Call>) {
|
||||
for (call in deletedCalls) {
|
||||
ApplicationDependencies.getJobManager().add(
|
||||
createForDelete(
|
||||
call.peer,
|
||||
call.callId,
|
||||
call.direction == CallTable.Direction.INCOMING
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getParameters(conversationRecipientId: RecipientId): Parameters {
|
||||
return Parameters.Builder()
|
||||
.setQueue(conversationRecipientId.toQueueKey())
|
||||
.setLifespan(TimeUnit.MINUTES.toMillis(5))
|
||||
.setMaxAttempts(3)
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
override fun serialize(): ByteArray? {
|
||||
return JsonJobData.Builder()
|
||||
.putLong(KEY_CALL_ID, callId)
|
||||
.putString(KEY_CONVERSATION_ID, conversationRecipientId.serialize())
|
||||
.putInt(KEY_EVENT, CallTable.Event.serialize(event))
|
||||
.putInt(KEY_DIRECTION, CallTable.Direction.serialize(direction))
|
||||
.serialize()
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
override fun onFailure() = Unit
|
||||
|
||||
override fun onRun() {
|
||||
val inputTimestamp = JsonJobData.deserialize(inputData).getLongOrDefault(GroupCallUpdateSendJob.KEY_SYNC_TIMESTAMP, System.currentTimeMillis())
|
||||
val syncTimestamp = if (inputTimestamp == 0L) System.currentTimeMillis() else inputTimestamp
|
||||
val syncMessage = CallEventSyncMessageUtil.createAcceptedSyncMessage(
|
||||
RemotePeer(conversationRecipientId, CallId(callId)),
|
||||
syncTimestamp,
|
||||
direction == CallTable.Direction.OUTGOING,
|
||||
true
|
||||
)
|
||||
|
||||
try {
|
||||
ApplicationDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(syncMessage), Optional.empty())
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Unable to send call event sync message for $callId", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShouldRetry(e: Exception): Boolean = false
|
||||
|
||||
class Factory : Job.Factory<CallSyncEventJob> {
|
||||
override fun create(parameters: Parameters, serializedData: ByteArray?): CallSyncEventJob {
|
||||
val data = JsonJobData.deserialize(serializedData)
|
||||
|
||||
return CallSyncEventJob(
|
||||
parameters,
|
||||
RecipientId.from(data.getString(KEY_CONVERSATION_ID)),
|
||||
data.getLong(KEY_CALL_ID),
|
||||
CallTable.Direction.deserialize(data.getInt(KEY_DIRECTION)),
|
||||
CallTable.Event.deserialize(data.getInt(KEY_EVENT))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,12 +42,15 @@ public class GroupCallUpdateSendJob extends BaseJob {
|
|||
private static final String KEY_ERA_ID = "era_id";
|
||||
private static final String KEY_RECIPIENTS = "recipients";
|
||||
private static final String KEY_INITIAL_RECIPIENT_COUNT = "initial_recipient_count";
|
||||
static final String KEY_SYNC_TIMESTAMP = "sync_timestamp";
|
||||
|
||||
private final RecipientId recipientId;
|
||||
private final String eraId;
|
||||
private final List<RecipientId> recipients;
|
||||
private final int initialRecipientCount;
|
||||
|
||||
private long syncTimestamp;
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull GroupCallUpdateSendJob create(@NonNull RecipientId recipientId, @Nullable String eraId) {
|
||||
Recipient conversationRecipient = Recipient.resolved(recipientId);
|
||||
|
@ -65,6 +68,7 @@ public class GroupCallUpdateSendJob extends BaseJob {
|
|||
eraId,
|
||||
recipientIds,
|
||||
recipientIds.size(),
|
||||
0L,
|
||||
new Parameters.Builder()
|
||||
.setQueue(conversationRecipient.getId().toQueueKey())
|
||||
.setLifespan(TimeUnit.MINUTES.toMillis(5))
|
||||
|
@ -76,6 +80,7 @@ public class GroupCallUpdateSendJob extends BaseJob {
|
|||
@Nullable String eraId,
|
||||
@NonNull List<RecipientId> recipients,
|
||||
int initialRecipientCount,
|
||||
long syncTimestamp,
|
||||
@NonNull Parameters parameters)
|
||||
{
|
||||
super(parameters);
|
||||
|
@ -84,6 +89,7 @@ public class GroupCallUpdateSendJob extends BaseJob {
|
|||
this.eraId = eraId;
|
||||
this.recipients = recipients;
|
||||
this.initialRecipientCount = initialRecipientCount;
|
||||
this.syncTimestamp = syncTimestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -92,6 +98,7 @@ public class GroupCallUpdateSendJob extends BaseJob {
|
|||
.putString(KEY_ERA_ID, eraId)
|
||||
.putString(KEY_RECIPIENTS, RecipientId.toSerializedList(recipients))
|
||||
.putInt(KEY_INITIAL_RECIPIENT_COUNT, initialRecipientCount)
|
||||
.putLong(KEY_SYNC_TIMESTAMP, syncTimestamp)
|
||||
.serialize();
|
||||
}
|
||||
|
||||
|
@ -125,6 +132,10 @@ public class GroupCallUpdateSendJob extends BaseJob {
|
|||
Log.w(TAG, "Still need to send to " + recipients.size() + " recipients. Retrying.");
|
||||
throw new RetryLaterException();
|
||||
}
|
||||
|
||||
setOutputData(new JsonJobData.Builder()
|
||||
.putLong(KEY_SYNC_TIMESTAMP, syncTimestamp)
|
||||
.serialize());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -166,6 +177,7 @@ public class GroupCallUpdateSendJob extends BaseJob {
|
|||
|
||||
if (includesSelf) {
|
||||
results.add(ApplicationDependencies.getSignalServiceMessageSender().sendSyncMessage(dataMessage));
|
||||
syncTimestamp = dataMessage.getTimestamp();
|
||||
}
|
||||
|
||||
return GroupSendJobHelper.getCompletedSends(destinations, results).completed;
|
||||
|
@ -182,8 +194,9 @@ public class GroupCallUpdateSendJob extends BaseJob {
|
|||
String eraId = data.getString(KEY_ERA_ID);
|
||||
List<RecipientId> recipients = RecipientId.fromSerializedList(data.getString(KEY_RECIPIENTS));
|
||||
int initialRecipientCount = data.getInt(KEY_INITIAL_RECIPIENT_COUNT);
|
||||
long syncTimestamp = data.getLongOrDefault(KEY_SYNC_TIMESTAMP, 0L);
|
||||
|
||||
return new GroupCallUpdateSendJob(recipientId, eraId, recipients, initialRecipientCount, parameters);
|
||||
return new GroupCallUpdateSendJob(recipientId, eraId, recipients, initialRecipientCount, syncTimestamp, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,6 +92,7 @@ public final class JobManagerFactories {
|
|||
put(AvatarGroupsV1DownloadJob.KEY, new AvatarGroupsV1DownloadJob.Factory());
|
||||
put(AvatarGroupsV2DownloadJob.KEY, new AvatarGroupsV2DownloadJob.Factory());
|
||||
put(BoostReceiptRequestResponseJob.KEY, new BoostReceiptRequestResponseJob.Factory());
|
||||
put(CallSyncEventJob.KEY, new CallSyncEventJob.Factory());
|
||||
put(CheckServiceReachabilityJob.KEY, new CheckServiceReachabilityJob.Factory());
|
||||
put(CleanPreKeysJob.KEY, new CleanPreKeysJob.Factory());
|
||||
put(ClearFallbackKbsEnclaveJob.KEY, new ClearFallbackKbsEnclaveJob.Factory());
|
||||
|
|
|
@ -934,7 +934,7 @@ object DataMessageProcessor {
|
|||
|
||||
val groupRecipientId = SignalDatabase.recipients.getOrInsertFromPossiblyMigratedGroupId(groupId)
|
||||
|
||||
SignalDatabase.messages.insertOrUpdateGroupCall(
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromExternalEvent(
|
||||
groupRecipientId,
|
||||
senderRecipientId,
|
||||
envelope.serverTimestamp,
|
||||
|
|
|
@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.messages;
|
|||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -316,7 +315,7 @@ public class MessageContentProcessor {
|
|||
else if (message.getGiftBadge().isPresent()) messageId = handleGiftMessage(content, message, senderRecipient, threadRecipient, receivedTime);
|
||||
else if (isMediaMessage) messageId = handleMediaMessage(content, message, smsMessageId, senderRecipient, threadRecipient, receivedTime);
|
||||
else if (message.getBody().isPresent()) messageId = handleTextMessage(content, message, smsMessageId, groupId, senderRecipient, threadRecipient, receivedTime);
|
||||
else if (Build.VERSION.SDK_INT > 19 && message.getGroupCallUpdate().isPresent()) handleGroupCallUpdateMessage(content, message, groupId, senderRecipient);
|
||||
else if (message.getGroupCallUpdate().isPresent()) handleGroupCallUpdateMessage(content, message, groupId, senderRecipient);
|
||||
|
||||
if (groupId.isPresent() && groupDatabase.isUnknownGroup(groupId.get())) {
|
||||
handleUnknownGroupMessage(content, message.getGroupContext().get(), senderRecipient);
|
||||
|
@ -369,7 +368,14 @@ public 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 if (syncMessage.getCallEvent().isPresent()) {
|
||||
SyncMessage.CallEvent.Type type = syncMessage.getCallEvent().get().getType();
|
||||
if (type == SyncMessage.CallEvent.Type.GROUP_CALL || type == SyncMessage.CallEvent.Type.AD_HOC_CALL) {
|
||||
handleSynchronizeGroupOrAdHocCallEvent(syncMessage.getCallEvent().get(), content.getTimestamp());
|
||||
} else {
|
||||
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...");
|
||||
|
@ -753,7 +759,7 @@ public class MessageContentProcessor {
|
|||
|
||||
RecipientId groupRecipientId = SignalDatabase.recipients().getOrInsertFromPossiblyMigratedGroupId(groupId.get());
|
||||
|
||||
SignalDatabase.messages().insertOrUpdateGroupCall(groupRecipientId,
|
||||
SignalDatabase.calls().insertOrUpdateGroupCallFromExternalEvent(groupRecipientId,
|
||||
senderRecipient.getId(),
|
||||
content.getServerReceivedTimestamp(),
|
||||
message.getGroupCallUpdate().get().getEraId());
|
||||
|
@ -1274,12 +1280,12 @@ public class MessageContentProcessor {
|
|||
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());
|
||||
if (timestamp == 0 || type == null || direction == null || event == null || !callEvent.hasConversationId()) {
|
||||
warn(envelopeTimestamp, "Call event sync message is not valid, ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId());
|
||||
return;
|
||||
}
|
||||
|
||||
ServiceId serviceId = ServiceId.fromByteString(callEvent.getPeerUuid());
|
||||
ServiceId serviceId = ServiceId.fromByteString(callEvent.getConversationId());
|
||||
RecipientId recipientId = RecipientId.from(serviceId);
|
||||
|
||||
log(envelopeTimestamp, "Synchronize call event call: " + callId);
|
||||
|
@ -1294,10 +1300,79 @@ public class MessageContentProcessor {
|
|||
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);
|
||||
SignalDatabase.calls().updateOneToOneCall(callId, event);
|
||||
}
|
||||
} else {
|
||||
SignalDatabase.calls().insertCall(callId, timestamp, recipientId, type, direction, event);
|
||||
SignalDatabase.calls().insertOneToOneCall(callId, timestamp, recipientId, type, direction, event);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSynchronizeGroupOrAdHocCallEvent(@NonNull SyncMessage.CallEvent callEvent, long envelopeTimestamp)
|
||||
throws BadGroupIdException
|
||||
{
|
||||
if (!callEvent.hasId()) {
|
||||
log(envelopeTimestamp, "Synchronize group/ad-hoc call event missing call id, ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FeatureFlags.adHocCalling() && callEvent.getType() == SyncMessage.CallEvent.Type.AD_HOC_CALL) {
|
||||
log(envelopeTimestamp, "Ad-Hoc calling is not currently supported by this client, 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.hasConversationId()) {
|
||||
warn(envelopeTimestamp, "Group/Ad-hoc call event sync message is not valid, ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId());
|
||||
return;
|
||||
}
|
||||
|
||||
CallTable.Call call = SignalDatabase.calls().getCallById(callId);
|
||||
if (call != null) {
|
||||
if (call.getType() != type) {
|
||||
warn(envelopeTimestamp, "Group/Ad-hoc call event type mismatch, ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId());
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case DELETE:
|
||||
SignalDatabase.calls().deleteGroupCall(call);
|
||||
break;
|
||||
case ACCEPTED:
|
||||
if (call.getTimestamp() < callEvent.getTimestamp()) {
|
||||
SignalDatabase.calls().setTimestamp(call.getCallId(), callEvent.getTimestamp());
|
||||
}
|
||||
|
||||
if (callEvent.getDirection() == SyncMessage.CallEvent.Direction.INCOMING) {
|
||||
SignalDatabase.calls().acceptIncomingGroupCall(call);
|
||||
} else {
|
||||
warn(envelopeTimestamp, "Invalid direction OUTGOING for event ACCEPTED");
|
||||
}
|
||||
|
||||
break;
|
||||
case NOT_ACCEPTED:
|
||||
default:
|
||||
warn("Unsupported event type " + event + ". Ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId());
|
||||
}
|
||||
} else {
|
||||
GroupId groupId = GroupId.push(callEvent.getConversationId().toByteArray());
|
||||
RecipientId recipientId = Recipient.externalGroupExact(groupId).getId();
|
||||
|
||||
switch (event) {
|
||||
case DELETE:
|
||||
SignalDatabase.calls().insertDeletedGroupCallFromSyncEvent(callEvent.getId(), recipientId, direction, timestamp);
|
||||
break;
|
||||
case ACCEPTED:
|
||||
SignalDatabase.calls().insertAcceptedGroupCall(callEvent.getId(), recipientId, direction, timestamp);
|
||||
break;
|
||||
case NOT_ACCEPTED:
|
||||
default:
|
||||
warn("Unsupported event type " + event + ". Ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1334,7 +1409,7 @@ public class MessageContentProcessor {
|
|||
} else if (dataMessage.isGroupV2Update()) {
|
||||
handleSynchronizeSentGv2Update(content, message);
|
||||
threadId = SignalDatabase.threads().getOrCreateThreadIdFor(getSyncMessageDestination(message));
|
||||
} else if (Build.VERSION.SDK_INT > 19 && dataMessage.getGroupCallUpdate().isPresent()) {
|
||||
} else if (dataMessage.getGroupCallUpdate().isPresent()) {
|
||||
handleGroupCallUpdateMessage(content, dataMessage, GroupUtil.idFromGroupContext(dataMessage.getGroupContext()), senderRecipient);
|
||||
} else if (dataMessage.isEmptyGroupV2Message()) {
|
||||
warn(content.getTimestamp(), "Empty GV2 message! Doing nothing.");
|
||||
|
|
|
@ -84,6 +84,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId
|
|||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.util.EarlyMessageCacheEntry
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
|
@ -980,22 +981,30 @@ object SyncMessageProcessor {
|
|||
|
||||
private fun handleSynchronizeCallEvent(callEvent: SyncMessage.CallEvent, envelopeTimestamp: Long) {
|
||||
if (!callEvent.hasId()) {
|
||||
log(envelopeTimestamp, "Synchronize call event missing call id, ignoring.")
|
||||
log(envelopeTimestamp, "Synchronize call event missing call id, ignoring. type: ${callEvent.type}")
|
||||
return
|
||||
}
|
||||
|
||||
if (callEvent.type == SyncMessage.CallEvent.Type.GROUP_CALL || callEvent.type == SyncMessage.CallEvent.Type.AD_HOC_CALL) {
|
||||
handleSynchronizeGroupOrAdHocCallEvent(callEvent, envelopeTimestamp)
|
||||
} else {
|
||||
handleSynchronizeOneToOneCallEvent(callEvent, envelopeTimestamp)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSynchronizeOneToOneCallEvent(callEvent: SyncMessage.CallEvent, envelopeTimestamp: Long) {
|
||||
val callId: Long = callEvent.id
|
||||
val timestamp: Long = callEvent.timestamp
|
||||
val type: CallTable.Type? = CallTable.Type.from(callEvent.type)
|
||||
val direction: CallTable.Direction? = CallTable.Direction.from(callEvent.direction)
|
||||
val event: CallTable.Event? = CallTable.Event.from(callEvent.event)
|
||||
|
||||
if (timestamp == 0L || 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())
|
||||
if (timestamp == 0L || type == null || direction == null || event == null || !callEvent.hasConversationId()) {
|
||||
warn(envelopeTimestamp, "Call event sync message is not valid, ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId())
|
||||
return
|
||||
}
|
||||
|
||||
val serviceId = ServiceId.fromByteString(callEvent.peerUuid)
|
||||
val serviceId = ServiceId.fromByteString(callEvent.conversationId)
|
||||
val recipientId = RecipientId.from(serviceId)
|
||||
|
||||
log(envelopeTimestamp, "Synchronize call event call: $callId")
|
||||
|
@ -1010,10 +1019,62 @@ object SyncMessageProcessor {
|
|||
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)
|
||||
SignalDatabase.calls.updateOneToOneCall(callId, event)
|
||||
}
|
||||
} else {
|
||||
SignalDatabase.calls.insertCall(callId, timestamp, recipientId, type, direction, event)
|
||||
SignalDatabase.calls.insertOneToOneCall(callId, timestamp, recipientId, type, direction, event)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(BadGroupIdException::class)
|
||||
private fun handleSynchronizeGroupOrAdHocCallEvent(callEvent: SyncMessage.CallEvent, envelopeTimestamp: Long) {
|
||||
if (!FeatureFlags.adHocCalling() && callEvent.type == SyncMessage.CallEvent.Type.AD_HOC_CALL) {
|
||||
log(envelopeTimestamp, "Ad-Hoc calling is not currently supported by this client, ignoring.")
|
||||
return
|
||||
}
|
||||
|
||||
val callId: Long = callEvent.id
|
||||
val timestamp: Long = callEvent.timestamp
|
||||
val type: CallTable.Type? = CallTable.Type.from(callEvent.type)
|
||||
val direction: CallTable.Direction? = CallTable.Direction.from(callEvent.direction)
|
||||
val event: CallTable.Event? = CallTable.Event.from(callEvent.event)
|
||||
|
||||
if (timestamp == 0L || type == null || direction == null || event == null || !callEvent.hasConversationId()) {
|
||||
warn(envelopeTimestamp, "Group/Ad-hoc call event sync message is not valid, ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId())
|
||||
return
|
||||
}
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId)
|
||||
|
||||
if (call != null) {
|
||||
if (call.type !== type) {
|
||||
warn(envelopeTimestamp, "Group/Ad-hoc call event type mismatch, ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId())
|
||||
return
|
||||
}
|
||||
when (event) {
|
||||
CallTable.Event.DELETE -> SignalDatabase.calls.deleteGroupCall(call)
|
||||
CallTable.Event.ACCEPTED -> {
|
||||
if (call.timestamp < callEvent.timestamp) {
|
||||
SignalDatabase.calls.setTimestamp(call.callId, callEvent.timestamp)
|
||||
}
|
||||
if (callEvent.direction == SyncMessage.CallEvent.Direction.INCOMING) {
|
||||
SignalDatabase.calls.acceptIncomingGroupCall(call)
|
||||
} else {
|
||||
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: " + callEvent.hasConversationId())
|
||||
else -> warn("Unsupported event type " + event + ". Ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId())
|
||||
}
|
||||
} else {
|
||||
val groupId: GroupId = GroupId.push(callEvent.conversationId.toByteArray())
|
||||
val recipientId = Recipient.externalGroupExact(groupId).id
|
||||
when (event) {
|
||||
CallTable.Event.DELETE -> SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(callEvent.id, recipientId, direction, timestamp)
|
||||
CallTable.Event.ACCEPTED -> SignalDatabase.calls.insertAcceptedGroupCall(callEvent.id, recipientId, direction, timestamp)
|
||||
CallTable.Event.NOT_ACCEPTED -> warn("Unsupported event type " + event + ". Ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId())
|
||||
else -> warn("Unsupported event type " + event + ". Ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
package org.thoughtcrime.securesms.service
|
||||
|
||||
import android.app.Application
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Manages deleting call events 8 hours after they've been marked deleted.
|
||||
*/
|
||||
class DeletedCallEventManager(
|
||||
application: Application
|
||||
) : TimedEventManager<DeletedCallEventManager.Event>(application, "ExpiringCallEventsManager") {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(DeletedCallEventManager::class.java)
|
||||
|
||||
private val CALL_EVENT_DELETION_LIFESPAN = TimeUnit.HOURS.toMillis(8)
|
||||
}
|
||||
|
||||
init {
|
||||
scheduleIfNecessary()
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
override fun getNextClosestEvent(): Event? {
|
||||
val oldestTimestamp = SignalDatabase.calls.getOldestDeletionTimestamp()
|
||||
if (oldestTimestamp <= 0) return null
|
||||
|
||||
val timeSinceSend = System.currentTimeMillis() - oldestTimestamp
|
||||
val delay = (CALL_EVENT_DELETION_LIFESPAN - timeSinceSend).coerceAtLeast(0)
|
||||
Log.i(TAG, "The oldest call event needs to be deleted in $delay ms.")
|
||||
|
||||
return Event(delay)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
override fun executeEvent(event: Event) {
|
||||
val threshold = System.currentTimeMillis() - CALL_EVENT_DELETION_LIFESPAN
|
||||
val deletes = SignalDatabase.calls.deleteCallEventsDeletedBefore(threshold)
|
||||
Log.i(TAG, "Deleted $deletes call events before $threshold")
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
override fun getDelayForEvent(event: Event): Long = event.delay
|
||||
|
||||
@WorkerThread
|
||||
override fun scheduleAlarm(application: Application, event: Event, delay: Long) {
|
||||
setAlarm(application, delay, DeleteCallEventsAlarm::class.java)
|
||||
}
|
||||
|
||||
data class Event(val delay: Long)
|
||||
|
||||
class DeleteCallEventsAlarm : BroadcastReceiver() {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(DeleteCallEventsAlarm::class.java)
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
Log.d(TAG, "onReceive()")
|
||||
ApplicationDependencies.getDeletedCallEventManager().scheduleIfNecessary()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
package org.thoughtcrime.securesms.service.webrtc
|
||||
|
||||
import com.google.protobuf.ByteString
|
||||
import org.thoughtcrime.securesms.database.model.toProtoByteString
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.CallEvent
|
||||
|
||||
|
@ -10,27 +13,70 @@ import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMe
|
|||
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()
|
||||
return createCallEvent(
|
||||
remotePeer.id,
|
||||
remotePeer.callId.longValue(),
|
||||
timestamp,
|
||||
isOutgoing,
|
||||
isVideoCall,
|
||||
CallEvent.Event.ACCEPTED
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun createNotAcceptedSyncMessage(remotePeer: RemotePeer, timestamp: Long, isOutgoing: Boolean, isVideoCall: Boolean): CallEvent {
|
||||
return createCallEvent(
|
||||
remotePeer.id,
|
||||
remotePeer.callId.longValue(),
|
||||
timestamp,
|
||||
isOutgoing,
|
||||
isVideoCall,
|
||||
CallEvent.Event.NOT_ACCEPTED
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun createDeleteCallEvent(remotePeer: RemotePeer, timestamp: Long, isOutgoing: Boolean, isVideoCall: Boolean): CallEvent {
|
||||
return createCallEvent(
|
||||
remotePeer.id,
|
||||
remotePeer.callId.longValue(),
|
||||
timestamp,
|
||||
isOutgoing,
|
||||
isVideoCall,
|
||||
CallEvent.Event.DELETE
|
||||
)
|
||||
}
|
||||
|
||||
private fun createCallEvent(
|
||||
recipientId: RecipientId,
|
||||
callId: Long,
|
||||
timestamp: Long,
|
||||
isOutgoing: Boolean,
|
||||
isVideoCall: Boolean,
|
||||
event: CallEvent.Event
|
||||
): CallEvent {
|
||||
val recipient = Recipient.resolved(recipientId)
|
||||
val isGroupCall = recipient.isGroup
|
||||
val conversationId: ByteString = if (isGroupCall) {
|
||||
recipient.requireGroupId().decodedId.toProtoByteString()
|
||||
} else {
|
||||
recipient.requireServiceId().toByteString()
|
||||
}
|
||||
|
||||
return CallEvent
|
||||
.newBuilder()
|
||||
.setPeerUuid(Recipient.resolved(remotePeer.id).requireServiceId().toByteString())
|
||||
.setId(remotePeer.callId.longValue())
|
||||
.setConversationId(conversationId)
|
||||
.setId(callId)
|
||||
.setTimestamp(timestamp)
|
||||
.setType(if (isVideoCall) CallEvent.Type.VIDEO_CALL else CallEvent.Type.AUDIO_CALL)
|
||||
.setType(
|
||||
when {
|
||||
isGroupCall -> CallEvent.Type.GROUP_CALL
|
||||
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)
|
||||
.setEvent(event)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.events.CallParticipant;
|
|||
import org.thoughtcrime.securesms.events.CallParticipantId;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.ringrtc.Camera;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcEphemeralState;
|
||||
|
@ -148,8 +149,9 @@ public class GroupConnectedActionProcessor extends GroupActionProcessor {
|
|||
return currentState;
|
||||
}
|
||||
|
||||
boolean remoteUserRangTheCall = currentState.getCallSetupState(RemotePeer.GROUP_CALL_ID).getRingerRecipient() != Recipient.self();
|
||||
String eraId = WebRtcUtil.getGroupCallEraId(groupCall);
|
||||
webRtcInteractor.sendGroupCallMessage(currentState.getCallInfoState().getCallRecipient(), eraId);
|
||||
webRtcInteractor.sendGroupCallMessage(currentState.getCallInfoState().getCallRecipient(), eraId, remoteUserRangTheCall, true);
|
||||
|
||||
List<UUID> members = new ArrayList<>(peekInfo.getJoinedMembers());
|
||||
if (!members.contains(SignalStore.account().requireAci().uuid())) {
|
||||
|
@ -176,7 +178,7 @@ public class GroupConnectedActionProcessor extends GroupActionProcessor {
|
|||
}
|
||||
|
||||
String eraId = WebRtcUtil.getGroupCallEraId(groupCall);
|
||||
webRtcInteractor.sendGroupCallMessage(currentState.getCallInfoState().getCallRecipient(), eraId);
|
||||
webRtcInteractor.sendGroupCallMessage(currentState.getCallInfoState().getCallRecipient(), eraId, false, false);
|
||||
|
||||
List<UUID> members = Stream.of(currentState.getCallInfoState().getRemoteCallParticipants()).map(p -> p.getRecipient().requireServiceId().uuid()).toList();
|
||||
webRtcInteractor.updateGroupCallUpdateMessage(currentState.getCallInfoState().getCallRecipient().getId(), eraId, members, false);
|
||||
|
|
|
@ -102,9 +102,9 @@ public class IdleActionProcessor extends WebRtcActionProcessor {
|
|||
}
|
||||
|
||||
if (ringUpdate != CallManager.RingUpdate.REQUESTED) {
|
||||
SignalDatabase.groupCallRings().insertOrUpdateGroupRing(ringId, System.currentTimeMillis(), ringUpdate);
|
||||
SignalDatabase.calls().insertOrUpdateGroupCallFromRingState(ringId, remotePeerGroup.getId(), sender, System.currentTimeMillis(), ringUpdate);
|
||||
return currentState;
|
||||
} else if (SignalDatabase.groupCallRings().isCancelled(ringId)) {
|
||||
} else if (SignalDatabase.calls().isRingCancelled(ringId)) {
|
||||
try {
|
||||
Log.i(TAG, "Incoming ring request for already cancelled ring: " + ringId);
|
||||
webRtcInteractor.getCallManager().cancelGroupRing(groupId.getDecodedId(), ringId, null);
|
||||
|
@ -118,7 +118,7 @@ public class IdleActionProcessor extends WebRtcActionProcessor {
|
|||
if (activeProfile != null && !(activeProfile.isRecipientAllowed(remotePeerGroup.getId()) || activeProfile.getAllowAllCalls())) {
|
||||
try {
|
||||
Log.i(TAG, "Incoming ring request for profile restricted recipient");
|
||||
SignalDatabase.groupCallRings().insertOrUpdateGroupRing(ringId, System.currentTimeMillis(), CallManager.RingUpdate.EXPIRED_REQUEST);
|
||||
SignalDatabase.calls().insertOrUpdateGroupCallFromRingState(ringId, remotePeerGroup.getId(), sender, System.currentTimeMillis(), CallManager.RingUpdate.EXPIRED_REQUEST);
|
||||
webRtcInteractor.getCallManager().cancelGroupRing(groupId.getDecodedId(), ringId, CallManager.RingCancelReason.DeclinedByUser);
|
||||
} catch (CallException e) {
|
||||
Log.w(TAG, "Error while trying to cancel ring: " + ringId, e);
|
||||
|
@ -135,7 +135,7 @@ public class IdleActionProcessor extends WebRtcActionProcessor {
|
|||
protected @NonNull WebRtcServiceState handleReceivedGroupCallPeekForRingingCheck(@NonNull WebRtcServiceState currentState, @NonNull GroupCallRingCheckInfo info, @NonNull PeekInfo peekInfo) {
|
||||
Log.i(tag, "handleReceivedGroupCallPeekForRingingCheck(): recipient: " + info.getRecipientId() + " ring: " + info.getRingId());
|
||||
|
||||
if (SignalDatabase.groupCallRings().isCancelled(info.getRingId())) {
|
||||
if (SignalDatabase.calls().isRingCancelled(info.getRingId())) {
|
||||
try {
|
||||
Log.i(TAG, "Ring was cancelled while getting peek info ring: " + info.getRingId());
|
||||
webRtcInteractor.getCallManager().cancelGroupRing(info.getGroupId().getDecodedId(), info.getRingId(), null);
|
||||
|
@ -147,11 +147,11 @@ public class IdleActionProcessor extends WebRtcActionProcessor {
|
|||
|
||||
if (peekInfo.getDeviceCount() == 0) {
|
||||
Log.i(TAG, "No one in the group call, mark as expired and do not ring");
|
||||
SignalDatabase.groupCallRings().insertOrUpdateGroupRing(info.getRingId(), System.currentTimeMillis(), CallManager.RingUpdate.EXPIRED_REQUEST);
|
||||
SignalDatabase.calls().insertOrUpdateGroupCallFromRingState(info.getRingId(), info.getRecipientId(), info.getRingerUuid(), System.currentTimeMillis(), CallManager.RingUpdate.EXPIRED_REQUEST);
|
||||
return currentState;
|
||||
} else if (peekInfo.getJoinedMembers().contains(Recipient.self().requireServiceId().uuid())) {
|
||||
Log.i(TAG, "We are already in the call, mark accepted on another device and do not ring");
|
||||
SignalDatabase.groupCallRings().insertOrUpdateGroupRing(info.getRingId(), System.currentTimeMillis(), CallManager.RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE);
|
||||
SignalDatabase.calls().insertOrUpdateGroupCallFromRingState(info.getRingId(), info.getRecipientId(), info.getRingerUuid(), System.currentTimeMillis(), CallManager.RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE);
|
||||
return currentState;
|
||||
}
|
||||
|
||||
|
|
|
@ -176,7 +176,7 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor {
|
|||
|
||||
activePeer.localRinging();
|
||||
|
||||
SignalDatabase.calls().insertCall(remotePeer.getCallId().longValue(),
|
||||
SignalDatabase.calls().insertOneToOneCall(remotePeer.getCallId().longValue(),
|
||||
System.currentTimeMillis(),
|
||||
remotePeer.getId(),
|
||||
currentState.getCallSetupState(activePeer).isRemoteVideoOffer() ? CallTable.Type.VIDEO_CALL : CallTable.Type.AUDIO_CALL,
|
||||
|
|
|
@ -58,7 +58,7 @@ public final class IncomingGroupCallActionProcessor extends DeviceAwareActionPro
|
|||
boolean updateForCurrentRingId = ringId == currentState.getCallSetupState(RemotePeer.GROUP_CALL_ID).getRingId();
|
||||
boolean isCurrentlyRinging = currentState.getCallInfoState().getGroupCallState().isRinging();
|
||||
|
||||
if (SignalDatabase.groupCallRings().isCancelled(ringId)) {
|
||||
if (SignalDatabase.calls().isRingCancelled(ringId)) {
|
||||
try {
|
||||
Log.i(TAG, "Ignoring incoming ring request for already cancelled ring: " + ringId);
|
||||
webRtcInteractor.getCallManager().cancelGroupRing(groupId.getDecodedId(), ringId, null);
|
||||
|
@ -69,7 +69,11 @@ public final class IncomingGroupCallActionProcessor extends DeviceAwareActionPro
|
|||
}
|
||||
|
||||
if (ringUpdate != CallManager.RingUpdate.REQUESTED) {
|
||||
SignalDatabase.groupCallRings().insertOrUpdateGroupRing(ringId, System.currentTimeMillis(), ringUpdate);
|
||||
SignalDatabase.calls().insertOrUpdateGroupCallFromRingState(ringId,
|
||||
remotePeerGroup.getId(),
|
||||
sender,
|
||||
System.currentTimeMillis(),
|
||||
ringUpdate);
|
||||
|
||||
if (updateForCurrentRingId && isCurrentlyRinging) {
|
||||
Log.i(TAG, "Cancelling current ring: " + ringId);
|
||||
|
@ -104,7 +108,14 @@ public final class IncomingGroupCallActionProcessor extends DeviceAwareActionPro
|
|||
|
||||
Log.i(TAG, "Requesting new ring: " + ringId);
|
||||
|
||||
SignalDatabase.groupCallRings().insertGroupRing(ringId, System.currentTimeMillis(), ringUpdate);
|
||||
Recipient ringerRecipient = Recipient.externalPush(ServiceId.from(sender));
|
||||
SignalDatabase.calls().insertOrUpdateGroupCallFromRingState(
|
||||
ringId,
|
||||
remotePeerGroup.getId(),
|
||||
ringerRecipient.getId(),
|
||||
System.currentTimeMillis(),
|
||||
ringUpdate
|
||||
);
|
||||
|
||||
currentState = WebRtcVideoUtil.initializeVideo(context, webRtcInteractor.getCameraEventListener(), currentState, RemotePeer.GROUP_CALL_ID.longValue());
|
||||
|
||||
|
@ -138,7 +149,7 @@ public final class IncomingGroupCallActionProcessor extends DeviceAwareActionPro
|
|||
.changeCallSetupState(RemotePeer.GROUP_CALL_ID)
|
||||
.isRemoteVideoOffer(true)
|
||||
.ringId(ringId)
|
||||
.ringerRecipient(Recipient.externalPush(ServiceId.from(sender)))
|
||||
.ringerRecipient(ringerRecipient)
|
||||
.commit()
|
||||
.changeCallInfoState()
|
||||
.activePeer(new RemotePeer(currentState.getCallInfoState().getCallRecipient().getId(), RemotePeer.GROUP_CALL_ID))
|
||||
|
@ -226,8 +237,11 @@ public final class IncomingGroupCallActionProcessor extends DeviceAwareActionPro
|
|||
Recipient recipient = currentState.getCallInfoState().getCallRecipient();
|
||||
Optional<GroupId> groupId = recipient.getGroupId();
|
||||
long ringId = currentState.getCallSetupState(RemotePeer.GROUP_CALL_ID).getRingId();
|
||||
Recipient ringer = currentState.getCallSetupState(RemotePeer.GROUP_CALL_ID).getRingerRecipient();
|
||||
|
||||
SignalDatabase.groupCallRings().insertOrUpdateGroupRing(ringId,
|
||||
SignalDatabase.calls().insertOrUpdateGroupCallFromRingState(ringId,
|
||||
recipient.getId(),
|
||||
ringer.getId(),
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE);
|
||||
|
||||
|
|
|
@ -85,7 +85,7 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor {
|
|||
|
||||
RecipientUtil.setAndSendUniversalExpireTimerIfNecessary(context, Recipient.resolved(remotePeer.getId()), SignalDatabase.threads().getThreadIdIfExistsFor(remotePeer.getId()));
|
||||
|
||||
SignalDatabase.calls().insertCall(remotePeer.getCallId().longValue(),
|
||||
SignalDatabase.calls().insertOneToOneCall(remotePeer.getCallId().longValue(),
|
||||
System.currentTimeMillis(),
|
||||
remotePeer.getId(),
|
||||
isVideoCall ? CallTable.Type.VIDEO_CALL : CallTable.Type.AUDIO_CALL,
|
||||
|
|
|
@ -39,6 +39,8 @@ import org.thoughtcrime.securesms.events.GroupCallPeekEvent;
|
|||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobs.CallSyncEventJob;
|
||||
import org.thoughtcrime.securesms.jobs.GroupCallUpdateSendJob;
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
|
@ -349,8 +351,8 @@ private void processStateless(@NonNull Function1<WebRtcEphemeralState, WebRtcEph
|
|||
Long threadId = SignalDatabase.threads().getThreadIdFor(group.getId());
|
||||
|
||||
if (threadId != null) {
|
||||
SignalDatabase.messages()
|
||||
.updatePreviousGroupCall(threadId,
|
||||
SignalDatabase.calls()
|
||||
.updateGroupCallFromPeek(threadId,
|
||||
peekInfo.getEraId(),
|
||||
peekInfo.getJoinedMembers(),
|
||||
WebRtcUtil.isCallFull(peekInfo));
|
||||
|
@ -831,25 +833,25 @@ private void processStateless(@NonNull Function1<WebRtcEphemeralState, WebRtcEph
|
|||
|
||||
public void insertMissedCall(@NonNull RemotePeer remotePeer, long timestamp, boolean isVideoOffer) {
|
||||
CallTable.Call call = SignalDatabase.calls()
|
||||
.updateCall(remotePeer.getCallId().longValue(), CallTable.Event.MISSED);
|
||||
.updateOneToOneCall(remotePeer.getCallId().longValue(), CallTable.Event.MISSED);
|
||||
|
||||
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);
|
||||
.insertOneToOneCall(remotePeer.getCallId().longValue(), timestamp, remotePeer.getId(), type, CallTable.Direction.INCOMING, CallTable.Event.MISSED);
|
||||
}
|
||||
}
|
||||
|
||||
public void insertReceivedCall(@NonNull RemotePeer remotePeer, boolean isVideoOffer) {
|
||||
CallTable.Call call = SignalDatabase.calls()
|
||||
.updateCall(remotePeer.getCallId().longValue(), CallTable.Event.ACCEPTED);
|
||||
.updateOneToOneCall(remotePeer.getCallId().longValue(), CallTable.Event.ACCEPTED);
|
||||
|
||||
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);
|
||||
.insertOneToOneCall(remotePeer.getCallId().longValue(), System.currentTimeMillis(), remotePeer.getId(), type, CallTable.Direction.INCOMING, CallTable.Event.ACCEPTED);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -887,12 +889,27 @@ private void processStateless(@NonNull Function1<WebRtcEphemeralState, WebRtcEph
|
|||
});
|
||||
}
|
||||
|
||||
public void sendGroupCallUpdateMessage(@NonNull Recipient recipient, @Nullable String groupCallEraId) {
|
||||
SignalExecutors.BOUNDED.execute(() -> ApplicationDependencies.getJobManager().add(GroupCallUpdateSendJob.create(recipient.getId(), groupCallEraId)));
|
||||
public void sendGroupCallUpdateMessage(@NonNull Recipient recipient, @Nullable String groupCallEraId, boolean isIncoming, boolean isJoinEvent) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
GroupCallUpdateSendJob updateSendJob = GroupCallUpdateSendJob.create(recipient.getId(), groupCallEraId);
|
||||
JobManager.Chain chain = ApplicationDependencies.getJobManager().startChain(updateSendJob);
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
chain.enqueue();
|
||||
});
|
||||
}
|
||||
|
||||
public void updateGroupCallUpdateMessage(@NonNull RecipientId groupId, @Nullable String groupCallEraId, @NonNull Collection<UUID> joinedMembers, boolean isCallFull) {
|
||||
SignalExecutors.BOUNDED.execute(() -> SignalDatabase.messages().insertOrUpdateGroupCall(groupId,
|
||||
SignalExecutors.BOUNDED.execute(() -> SignalDatabase.calls().insertOrUpdateGroupCallFromLocalEvent(groupId,
|
||||
Recipient.self().getId(),
|
||||
System.currentTimeMillis(),
|
||||
groupCallEraId,
|
||||
|
@ -935,7 +952,7 @@ 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);
|
||||
.updateOneToOneCall(remotePeer.getCallId().longValue(), CallTable.Event.ACCEPTED);
|
||||
|
||||
if (TextSecurePreferences.isMultiDevice(context)) {
|
||||
networkExecutor.execute(() -> {
|
||||
|
@ -952,7 +969,7 @@ private void processStateless(@NonNull Function1<WebRtcEphemeralState, WebRtcEph
|
|||
public void sendNotAcceptedCallEventSyncMessage(@NonNull RemotePeer remotePeer, boolean isOutgoing, boolean isVideoCall) {
|
||||
SignalDatabase
|
||||
.calls()
|
||||
.updateCall(remotePeer.getCallId().longValue(), CallTable.Event.NOT_ACCEPTED);
|
||||
.updateOneToOneCall(remotePeer.getCallId().longValue(), CallTable.Event.NOT_ACCEPTED);
|
||||
|
||||
if (TextSecurePreferences.isMultiDevice(context)) {
|
||||
networkExecutor.execute(() -> {
|
||||
|
|
|
@ -54,6 +54,7 @@ import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
|||
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
@ -798,7 +799,9 @@ public abstract class WebRtcActionProcessor {
|
|||
if (ringUpdate != RingUpdate.BUSY_LOCALLY && ringUpdate != RingUpdate.BUSY_ON_ANOTHER_DEVICE) {
|
||||
webRtcInteractor.getCallManager().cancelGroupRing(groupId.getDecodedId(), ringId, CallManager.RingCancelReason.Busy);
|
||||
}
|
||||
SignalDatabase.groupCallRings().insertOrUpdateGroupRing(ringId,
|
||||
SignalDatabase.calls().insertOrUpdateGroupCallFromRingState(ringId,
|
||||
remotePeerGroup.getId(),
|
||||
sender,
|
||||
System.currentTimeMillis(),
|
||||
ringUpdate == RingUpdate.REQUESTED ? RingUpdate.BUSY_LOCALLY : ringUpdate);
|
||||
} catch (CallException e) {
|
||||
|
@ -826,7 +829,7 @@ public abstract class WebRtcActionProcessor {
|
|||
Recipient recipient = currentState.getCallInfoState().getCallRecipient();
|
||||
|
||||
if (recipient != null && currentState.getCallInfoState().getGroupCallState().isConnected()) {
|
||||
webRtcInteractor.sendGroupCallMessage(recipient, WebRtcUtil.getGroupCallEraId(groupCall));
|
||||
webRtcInteractor.sendGroupCallMessage(recipient, WebRtcUtil.getGroupCallEraId(groupCall), false, false);
|
||||
}
|
||||
|
||||
currentState = currentState.builder()
|
||||
|
|
|
@ -84,8 +84,8 @@ public class WebRtcInteractor {
|
|||
signalCallManager.sendCallMessage(remotePeer, callMessage);
|
||||
}
|
||||
|
||||
void sendGroupCallMessage(@NonNull Recipient recipient, @Nullable String groupCallEraId) {
|
||||
signalCallManager.sendGroupCallUpdateMessage(recipient, groupCallEraId);
|
||||
void sendGroupCallMessage(@NonNull Recipient recipient, @Nullable String groupCallEraId, boolean isIncoming, boolean isJoinEvent) {
|
||||
signalCallManager.sendGroupCallUpdateMessage(recipient, groupCallEraId, isIncoming, isJoinEvent);
|
||||
}
|
||||
|
||||
void updateGroupCallUpdateMessage(@NonNull RecipientId groupId, @Nullable String groupCallEraId, @NonNull Collection<UUID> joinedMembers, boolean isCallFull) {
|
||||
|
|
|
@ -109,6 +109,7 @@ public final class FeatureFlags {
|
|||
private static final String CALLS_TAB = "android.calls.tab";
|
||||
private static final String TEXT_FORMATTING_SPOILER_SEND = "android.textFormatting.spoilerSend";
|
||||
private static final String EXPORT_ACCOUNT_DATA = "android.exportAccountData";
|
||||
private static final String AD_HOC_CALLING = "android.calling.ad.hoc";
|
||||
|
||||
/**
|
||||
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
||||
|
@ -173,7 +174,8 @@ public final class FeatureFlags {
|
|||
|
||||
@VisibleForTesting
|
||||
static final Set<String> NOT_REMOTE_CAPABLE = SetUtil.newHashSet(
|
||||
PHONE_NUMBER_PRIVACY
|
||||
PHONE_NUMBER_PRIVACY,
|
||||
AD_HOC_CALLING
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -612,6 +614,13 @@ public final class FeatureFlags {
|
|||
return getBoolean(EXPORT_ACCOUNT_DATA, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not ad-hoc calling is enabled
|
||||
*/
|
||||
public static boolean adHocCalling() {
|
||||
return getBoolean(AD_HOC_CALLING, false);
|
||||
}
|
||||
|
||||
/** Only for rendering debug info. */
|
||||
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
||||
return new TreeMap<>(REMOTE_VALUES);
|
||||
|
|
|
@ -5830,5 +5830,13 @@
|
|||
<!-- Accessibility label describing the continue button on the camera screen -->
|
||||
<string name="CameraControls_continue_button_accessibility_label">Continue Button</string>
|
||||
|
||||
<!-- CallPreference -->
|
||||
<!-- Missed group call in call info -->
|
||||
<string name="CallPreference__missed_group_call">Missed group call</string>
|
||||
<!-- Incoming group call in call info -->
|
||||
<string name="CallPreference__incoming_group_call">Incoming group call</string>
|
||||
<!-- Outgoing group call in call info -->
|
||||
<string name="CallPreference__outgoing_group_call">Outgoing group call</string>
|
||||
|
||||
<!-- EOF -->
|
||||
</resources>
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.payments.Payments;
|
|||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||
import org.thoughtcrime.securesms.recipients.LiveRecipientCache;
|
||||
import org.thoughtcrime.securesms.revealable.ViewOnceMessageManager;
|
||||
import org.thoughtcrime.securesms.service.DeletedCallEventManager;
|
||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
import org.thoughtcrime.securesms.service.ExpiringStoriesManager;
|
||||
import org.thoughtcrime.securesms.service.PendingRetryReceiptManager;
|
||||
|
@ -136,6 +137,11 @@ public class MockApplicationDependencyProvider implements ApplicationDependencie
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull DeletedCallEventManager provideDeletedCallEventManager() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull TypingStatusRepository provideTypingStatusRepository() {
|
||||
return null;
|
||||
|
|
|
@ -607,6 +607,8 @@ message SyncMessage {
|
|||
UNKNOWN_TYPE = 0;
|
||||
AUDIO_CALL = 1;
|
||||
VIDEO_CALL = 2;
|
||||
GROUP_CALL = 3;
|
||||
AD_HOC_CALL = 4;
|
||||
}
|
||||
|
||||
enum Direction {
|
||||
|
@ -619,9 +621,10 @@ message SyncMessage {
|
|||
UNKNOWN_ACTION = 0;
|
||||
ACCEPTED = 1;
|
||||
NOT_ACCEPTED = 2;
|
||||
DELETE = 3;
|
||||
}
|
||||
|
||||
optional bytes peerUuid = 1;
|
||||
optional bytes conversationId = 1;
|
||||
optional uint64 id = 2;
|
||||
optional uint64 timestamp = 3;
|
||||
optional Type type = 4;
|
||||
|
|
Loading…
Add table
Reference in a new issue