mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-08-04 20:34:14 +01:00
Various fixes for disappearing calls.
This commit is contained in:
@@ -10,9 +10,14 @@ import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.signal.ringrtc.CallId
|
||||
import org.signal.ringrtc.CallManager
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.testing.Flag
|
||||
import org.thoughtcrime.securesms.testing.RemoteConfigForTest
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import org.thoughtcrime.securesms.testing.TestRemoteConfigFlag
|
||||
|
||||
@RemoteConfigForTest(flags = [Flag(TestRemoteConfigFlag.DISAPPEAR_MORE, "true")])
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CallTableTest {
|
||||
|
||||
@@ -977,6 +982,80 @@ class CallTableTest {
|
||||
// assertEquals(0, allCallEvents.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAMissedOneToOneCall_whenIMarkAllCallEventsRead_thenTimerShouldStart() {
|
||||
val callId = 1L
|
||||
val peer = harness.others[0]
|
||||
insertExpiringThread(peer)
|
||||
|
||||
SignalDatabase.calls.insertOneToOneCall(callId, System.currentTimeMillis(), peer, CallTable.Type.AUDIO_CALL, CallTable.Direction.INCOMING, CallTable.Event.MISSED)
|
||||
|
||||
val readAt = System.currentTimeMillis()
|
||||
SignalDatabase.calls.markAllCallEventsRead(readAt = readAt)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, peer)
|
||||
assertEquals(readAt, SignalDatabase.messages.getMessageRecord(call!!.messageId!!).expireStarted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAMissedNotificationProfileOneToOneCall_whenIMarkAllCallEventsRead_thenTimerShouldStart() {
|
||||
val callId = 1L
|
||||
val peer = harness.others[0]
|
||||
insertExpiringThread(peer)
|
||||
|
||||
SignalDatabase.calls.insertOneToOneCall(callId, System.currentTimeMillis(), peer, CallTable.Type.AUDIO_CALL, CallTable.Direction.INCOMING, CallTable.Event.MISSED_NOTIFICATION_PROFILE)
|
||||
|
||||
val readAt = System.currentTimeMillis()
|
||||
SignalDatabase.calls.markAllCallEventsRead(readAt = readAt)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, peer)
|
||||
assertEquals(readAt, SignalDatabase.messages.getMessageRecord(call!!.messageId!!).expireStarted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAMissedGroupCall_whenIMarkAllCallEventsRead_thenTimerShouldStart() {
|
||||
val callId = 1L
|
||||
SignalDatabase.recipients.setExpireMessagesForGroup(groupRecipientId, 60)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(callId, groupRecipientId, harness.others[1], System.currentTimeMillis(), CallManager.RingUpdate.EXPIRED_REQUEST)
|
||||
|
||||
val readAt = System.currentTimeMillis()
|
||||
SignalDatabase.calls.markAllCallEventsRead(readAt = readAt)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
assertEquals(readAt, SignalDatabase.messages.getMessageRecord(call!!.messageId!!).expireStarted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAMissedNotificationProfileGroupCall_whenIMarkAllCallEventsRead_thenTimerShouldStart() {
|
||||
val callId = 1L
|
||||
val ringerAci = Recipient.resolved(harness.others[1]).requireAci()
|
||||
|
||||
SignalDatabase.recipients.setExpireMessagesForGroup(groupRecipientId, 60)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(callId, groupRecipientId, ringerAci, System.currentTimeMillis(), CallManager.RingUpdate.EXPIRED_REQUEST, true)
|
||||
|
||||
val readAt = System.currentTimeMillis()
|
||||
SignalDatabase.calls.markAllCallEventsRead(readAt = readAt)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
assertEquals(readAt, SignalDatabase.messages.getMessageRecord(call!!.messageId!!).expireStarted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAnOutgoingOneToOneCallFromSync_whenInserted_thenTimerIsStarted() {
|
||||
val callId = 1L
|
||||
val peer = harness.others[0]
|
||||
val timestamp = System.currentTimeMillis()
|
||||
|
||||
insertExpiringThread(peer)
|
||||
SignalDatabase.calls.insertOneToOneCall(callId, timestamp, peer, CallTable.Type.AUDIO_CALL, CallTable.Direction.OUTGOING, CallTable.Event.OUTGOING_RING, true)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, peer)
|
||||
val message = SignalDatabase.messages.getMessageRecord(call!!.messageId!!)
|
||||
assertNotEquals(0L, message.expireStarted)
|
||||
}
|
||||
|
||||
private fun insertTwoCallEvents() {
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
1,
|
||||
@@ -992,4 +1071,10 @@ class CallTableTest {
|
||||
2000
|
||||
)
|
||||
}
|
||||
|
||||
private fun insertExpiringThread(recipientId: RecipientId) {
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(recipientId))
|
||||
MmsHelper.insert(recipient = Recipient.resolved(recipientId), expiresIn = 30_000L, threadId = threadId)
|
||||
SignalDatabase.threads.update(threadId, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ annotation class RawFlag(val key: String, val value: String)
|
||||
*/
|
||||
enum class TestRemoteConfigFlag(private val property: KProperty0<*>) {
|
||||
INTERNAL_USER(RemoteConfig::internalUser),
|
||||
DEFAULT_MAX_BACKOFF(RemoteConfig::defaultMaxBackoff);
|
||||
DEFAULT_MAX_BACKOFF(RemoteConfig::defaultMaxBackoff),
|
||||
DISAPPEAR_MORE(RemoteConfig::disappearMore);
|
||||
|
||||
val key: String
|
||||
get() {
|
||||
|
||||
@@ -109,13 +109,11 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
||||
"CREATE INDEX $CALL_LOG_INDEX ON $TABLE_NAME ($TIMESTAMP, $PEER, $EVENT, $TYPE, $DELETION_TIMESTAMP)"
|
||||
)
|
||||
}
|
||||
fun markAllCallEventsRead(timestamp: Long = Long.MAX_VALUE) {
|
||||
val proposedExpireStarted = if (timestamp == Long.MAX_VALUE) System.currentTimeMillis() else timestamp
|
||||
|
||||
fun markAllCallEventsRead(timestamp: Long = Long.MAX_VALUE, readAt: Long = System.currentTimeMillis()) {
|
||||
val allUnreadMissedCalls = readableDatabase
|
||||
.select(MESSAGE_ID)
|
||||
.from(TABLE_NAME)
|
||||
.where("$TIMESTAMP <= ? AND $READ != ? AND $EVENT = ?", timestamp, ReadState.serialize(ReadState.READ), Event.serialize(Event.MISSED))
|
||||
.where("$TIMESTAMP <= ? AND $READ != ? AND ($EVENT = ? OR $EVENT = ?)", timestamp, ReadState.serialize(ReadState.READ), Event.serialize(Event.MISSED), Event.serialize(Event.MISSED_NOTIFICATION_PROFILE))
|
||||
.run()
|
||||
.readToList { cursor ->
|
||||
cursor.requireLong(MESSAGE_ID)
|
||||
@@ -131,9 +129,9 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
||||
|
||||
if (expiringCalls.isNotEmpty()) {
|
||||
Log.i(TAG, "Found ${expiringCalls.size} calls that needs expiring.")
|
||||
SignalDatabase.messages.markExpireStarted(expiringCalls.map { it.key to proposedExpireStarted })
|
||||
SignalDatabase.messages.markExpireStarted(expiringCalls.map { it.key to readAt })
|
||||
for ((messageId, expiresIn) in expiringCalls) {
|
||||
AppDependencies.expiringMessageManager.scheduleDeletion(messageId, true, proposedExpireStarted, expiresIn)
|
||||
AppDependencies.expiringMessageManager.scheduleDeletion(messageId, true, readAt, expiresIn)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,14 +140,13 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
||||
}
|
||||
}
|
||||
|
||||
fun markAllCallEventsWithPeerBeforeTimestampRead(peer: RecipientId, timestamp: Long): Call? {
|
||||
val proposedExpireStarted = if (timestamp == Long.MAX_VALUE) System.currentTimeMillis() else timestamp
|
||||
fun markAllCallEventsWithPeerBeforeTimestampRead(peer: RecipientId, timestamp: Long, readAt: Long = System.currentTimeMillis()): Call? {
|
||||
val latestCallAsOfTimestamp = writableDatabase.withinTransaction { db ->
|
||||
|
||||
val unreadMissedCalls = db
|
||||
.select(MESSAGE_ID)
|
||||
.from(TABLE_NAME)
|
||||
.where("$PEER = ? AND $TIMESTAMP <= ? AND $READ != ? AND $EVENT = ? AND $GROUP_CALL_ACTIVE = 0", peer.toLong(), timestamp, ReadState.serialize(ReadState.READ), Event.serialize(Event.MISSED))
|
||||
.where("$PEER = ? AND $TIMESTAMP <= ? AND $READ != ? AND ($EVENT = ? OR $EVENT = ?) AND $GROUP_CALL_ACTIVE = 0", peer.toLong(), timestamp, ReadState.serialize(ReadState.READ), Event.serialize(Event.MISSED), Event.serialize(Event.MISSED_NOTIFICATION_PROFILE))
|
||||
.run()
|
||||
.readToList { cursor ->
|
||||
cursor.requireLong(MESSAGE_ID)
|
||||
@@ -164,9 +161,9 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
||||
|
||||
if (expiring.isNotEmpty()) {
|
||||
Log.i(TAG, "Found ${expiring.size} calls that needs expiring.")
|
||||
SignalDatabase.messages.markExpireStarted(expiring.map { it.key to proposedExpireStarted })
|
||||
SignalDatabase.messages.markExpireStarted(expiring.map { it.key to readAt })
|
||||
for ((messageId, expiresIn) in expiring) {
|
||||
AppDependencies.expiringMessageManager.scheduleDeletion(messageId, true, proposedExpireStarted, expiresIn)
|
||||
AppDependencies.expiringMessageManager.scheduleDeletion(messageId, true, readAt, expiresIn)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +188,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
||||
return readableDatabase
|
||||
.count()
|
||||
.from(TABLE_NAME)
|
||||
.where("$EVENT = ? AND $READ = ?", Event.serialize(Event.MISSED), ReadState.serialize(ReadState.UNREAD))
|
||||
.where("($EVENT = ? OR $EVENT = ?) AND $READ = ?", Event.serialize(Event.MISSED), Event.serialize(Event.MISSED_NOTIFICATION_PROFILE), ReadState.serialize(ReadState.UNREAD))
|
||||
.run()
|
||||
.readToSingleLong()
|
||||
}
|
||||
@@ -1057,7 +1054,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
||||
|
||||
RingUpdate.DECLINED_ON_ANOTHER_DEVICE -> {
|
||||
when (call.event) {
|
||||
Event.RINGING, Event.MISSED, Event.MISSED_NOTIFICATION_PROFILE, Event.GENERIC_GROUP_CALL -> updateEventFromRingState(ringId, Event.DECLINED)
|
||||
Event.RINGING, Event.MISSED, Event.GENERIC_GROUP_CALL -> updateEventFromRingState(ringId, Event.DECLINED)
|
||||
Event.JOINED -> updateEventFromRingState(ringId, Event.ACCEPTED)
|
||||
Event.OUTGOING_RING -> Log.w(TAG, "Received DECLINED_ON_ANOTHER_DEVICE while in OUTGOING_RING state.")
|
||||
else -> Unit
|
||||
|
||||
@@ -915,6 +915,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
val threadId = threadIdResult.threadId
|
||||
val dateReceived = System.currentTimeMillis()
|
||||
val expiresIn = if (RemoteConfig.disappearMore) threads.getExpiresIn(threadId) else 0
|
||||
val missed = MessageTypes.isMissedAudioCall(type) || MessageTypes.isMissedVideoCall(type)
|
||||
|
||||
val values = contentValuesOf(
|
||||
FROM_RECIPIENT_ID to if (outgoing) Recipient.self().id.serialize() else recipientId.serialize(),
|
||||
@@ -923,7 +924,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
DATE_RECEIVED to dateReceived,
|
||||
DATE_SENT to timestamp,
|
||||
READ to 1,
|
||||
NOTIFIED to if (MessageTypes.isMissedAudioCall(type) || MessageTypes.isMissedVideoCall(type)) 0 else 1,
|
||||
NOTIFIED to if (missed) 0 else 1,
|
||||
TYPE to type,
|
||||
THREAD_ID to threadId,
|
||||
EXPIRES_IN to expiresIn
|
||||
@@ -938,8 +939,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
notifyConversationListeners(threadId)
|
||||
TrimThreadJob.enqueueAsync(threadId)
|
||||
|
||||
// If inserting an outgoing call from a sync message, automatically start timer
|
||||
if (expiresIn != 0L && outgoing && fromSync) {
|
||||
// If inserting a call from a sync message, automatically start timer unless it was missed
|
||||
if (expiresIn != 0L && !missed && fromSync) {
|
||||
Log.i(TAG, "Starting expiration timer after inserting a call from a sync message.")
|
||||
markExpireStarted(messageId, timestamp)
|
||||
AppDependencies.expiringMessageManager.scheduleDeletion(messageId, true, timestamp, expiresIn)
|
||||
@@ -997,7 +998,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
val expiresIn = if (RemoteConfig.disappearMore) recipient.expiresInSeconds.seconds.inWholeMilliseconds else 0
|
||||
val messageId: MessageId = writableDatabase.withinTransaction { db ->
|
||||
val self = Recipient.self()
|
||||
val markRead = joinedUuids.contains(self.requireServiceId().rawUuid) || self.id == sender
|
||||
val selfCreated = self.id == sender
|
||||
val markRead = joinedUuids.contains(self.requireServiceId().rawUuid) || selfCreated
|
||||
val updateDetails: ByteArray = GroupCallUpdateDetails(
|
||||
eraId = eraId,
|
||||
startedCallUuid = Recipient.resolved(sender).requireServiceId().toString(),
|
||||
@@ -1024,8 +1026,9 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
|
||||
val messageId = MessageId(db.insert(TABLE_NAME, null, values))
|
||||
|
||||
val isActiveCall = joinedUuids.isNotEmpty() || isIncomingGroupCallRingingOnLocalDevice
|
||||
if (!isActiveCall) {
|
||||
// Calls ringing from a linked (not local) device could be active
|
||||
val isPotentialActiveCall = joinedUuids.isNotEmpty() || isIncomingGroupCallRingingOnLocalDevice || selfCreated
|
||||
if (!isPotentialActiveCall) {
|
||||
maybeCollapseMessage(db = db, messageId = messageId.id, threadId = threadId, dateReceived = timestamp, messageExtras = null, messageType = MessageTypes.GROUP_CALL_TYPE)
|
||||
if (markRead && expiresIn != 0L) {
|
||||
Log.d(TAG, "[insertGroupCall] Starting expiration timer for group call.")
|
||||
@@ -1122,6 +1125,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
|
||||
val updateDetail = GroupCallUpdateDetailsUtil.parse(message.body)
|
||||
val containsSelf = joinedUuids.contains(SignalStore.account.requireAci().rawUuid)
|
||||
val selfCreated = updateDetail.startedCallUuid == SignalStore.account.requireAci().rawUuid.toString()
|
||||
// Treat empty eraId from ring requests as matching for updating
|
||||
val sameEraId = (updateDetail.eraId == eraId || updateDetail.eraId.isEmpty()) && !Util.isEmpty(eraId)
|
||||
val inCallUuids = if (sameEraId) joinedUuids.map { it.toString() } else emptyList()
|
||||
@@ -1140,7 +1144,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
val updated = db.update(TABLE_NAME, contentValues, query.where, query.whereArgs) > 0
|
||||
|
||||
if (inCallUuids.isEmpty()) {
|
||||
val acknowledgedCall = localJoined || event == Event.DECLINED
|
||||
val acknowledgedCall = localJoined || event == Event.DECLINED || selfCreated
|
||||
finalizeEndedGroupCallMessage(db, message, acknowledgedCall, logPrefix = "[updateGroupCall]")
|
||||
}
|
||||
|
||||
|
||||
@@ -1376,7 +1376,7 @@ object SyncMessageProcessor {
|
||||
}
|
||||
|
||||
CallLogEvent.Type.MARKED_AS_READ -> {
|
||||
SignalDatabase.calls.markAllCallEventsRead(timestamp)
|
||||
SignalDatabase.calls.markAllCallEventsRead(timestamp, envelopeTimestamp)
|
||||
}
|
||||
|
||||
CallLogEvent.Type.MARKED_AS_READ_IN_CONVERSATION -> {
|
||||
@@ -1385,7 +1385,7 @@ object SyncMessageProcessor {
|
||||
return
|
||||
}
|
||||
|
||||
SignalDatabase.calls.markAllCallEventsWithPeerBeforeTimestampRead(peer, timestamp)
|
||||
SignalDatabase.calls.markAllCallEventsWithPeerBeforeTimestampRead(peer, timestamp, envelopeTimestamp)
|
||||
}
|
||||
|
||||
else -> log(envelopeTimestamp, "Synchronize call log event has an invalid type $eventType, ignoring.")
|
||||
|
||||
@@ -110,7 +110,7 @@ public class MarkReadReceiver extends BroadcastReceiver {
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
for (RecipientId peer : peers) {
|
||||
CallTable.Call lastCallInThread = SignalDatabase.calls().markAllCallEventsWithPeerBeforeTimestampRead(peer, timestamp);
|
||||
CallTable.Call lastCallInThread = SignalDatabase.calls().markAllCallEventsWithPeerBeforeTimestampRead(peer, timestamp, System.currentTimeMillis());
|
||||
if (lastCallInThread != null) {
|
||||
AppDependencies.getJobManager().add(CallLogEventSendJob.forMarkedAsReadInConversation(lastCallInThread));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user