Improved missed call state handling.
This commit is contained in:
parent
95fbd7a31c
commit
e60b32202e
10 changed files with 128 additions and 10 deletions
|
@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.notifications.v2.ConversationId;
|
|||
import org.thoughtcrime.securesms.util.Debouncer;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SerialMonoLifoExecutor;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executor;
|
||||
|
@ -67,6 +68,7 @@ public class MarkReadHelper {
|
|||
|
||||
ApplicationDependencies.getMessageNotifier().updateNotification(context);
|
||||
MarkReadReceiver.process(infos);
|
||||
MarkReadReceiver.processCallEvents(Collections.singletonList(conversationId), timestamp);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -118,6 +118,30 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
notifyConversationListListeners()
|
||||
}
|
||||
|
||||
fun markAllCallEventsWithPeerBeforeTimestampRead(peer: RecipientId, timestamp: Long): Call? {
|
||||
val latestCallAsOfTimestamp = writableDatabase.withinTransaction { db ->
|
||||
val updated = db.update(TABLE_NAME)
|
||||
.values(READ to ReadState.serialize(ReadState.READ))
|
||||
.where("$PEER = ? AND $TIMESTAMP <= ?", peer.toLong(), timestamp)
|
||||
.run()
|
||||
|
||||
if (updated == 0) {
|
||||
null
|
||||
} else {
|
||||
db.select()
|
||||
.from(TABLE_NAME)
|
||||
.where("$PEER = ? AND $TIMESTAMP <= ?", peer.toLong(), timestamp)
|
||||
.orderBy("$TIMESTAMP DESC")
|
||||
.limit(1)
|
||||
.run()
|
||||
.readToSingleObject(Call.Deserializer)
|
||||
}
|
||||
}
|
||||
|
||||
notifyConversationListListeners()
|
||||
return latestCallAsOfTimestamp
|
||||
}
|
||||
|
||||
fun getUnreadMissedCallCount(): Long {
|
||||
return readableDatabase
|
||||
.count()
|
||||
|
|
|
@ -433,6 +433,12 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
|||
($TYPE = ${MessageTypes.GROUP_CALL_TYPE})
|
||||
)"""
|
||||
|
||||
private const val IS_MISSED_CALL_TYPE_CLAUSE = """(
|
||||
($TYPE = ${MessageTypes.MISSED_AUDIO_CALL_TYPE})
|
||||
OR
|
||||
($TYPE = ${MessageTypes.MISSED_VIDEO_CALL_TYPE})
|
||||
)"""
|
||||
|
||||
private val outgoingTypeClause: String by lazy {
|
||||
MessageTypes.OUTGOING_MESSAGE_TYPES
|
||||
.map { "($TABLE_NAME.$TYPE & ${MessageTypes.BASE_TYPE_MASK} = $it)" }
|
||||
|
@ -4647,7 +4653,19 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
|||
return readableDatabase
|
||||
.select(*MMS_PROJECTION)
|
||||
.from(TABLE_NAME)
|
||||
.where("$NOTIFIED = 0 AND $STORY_TYPE = 0 AND $LATEST_REVISION_ID IS NULL AND ($READ = 0 OR $REACTIONS_UNREAD = 1 ${if (stickyQuery.isNotEmpty()) "OR ($stickyQuery)" else ""})")
|
||||
.where(
|
||||
"""
|
||||
$NOTIFIED = 0
|
||||
AND $STORY_TYPE = 0
|
||||
AND $LATEST_REVISION_ID IS NULL
|
||||
AND (
|
||||
$READ = 0
|
||||
OR $REACTIONS_UNREAD = 1
|
||||
${if (stickyQuery.isNotEmpty()) "OR ($stickyQuery)" else ""}
|
||||
OR ($IS_MISSED_CALL_TYPE_CLAUSE AND EXISTS (SELECT 1 FROM ${CallTable.TABLE_NAME} WHERE ${CallTable.MESSAGE_ID} = $TABLE_NAME.$ID AND ${CallTable.EVENT} = ${CallTable.Event.serialize(CallTable.Event.MISSED)} AND ${CallTable.READ} = 0))
|
||||
)
|
||||
""".trimIndent()
|
||||
)
|
||||
.orderBy("$DATE_RECEIVED ASC")
|
||||
.run()
|
||||
}
|
||||
|
|
|
@ -86,6 +86,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V225_AddLocalUserJo
|
|||
import org.thoughtcrime.securesms.database.helpers.migration.V226_AddAttachmentMediaIdIndex
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V227_AddAttachmentArchiveTransferState
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V228_AddNameCollisionTables
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V229_MarkMissedCallEventsNotified
|
||||
|
||||
/**
|
||||
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
|
||||
|
@ -174,10 +175,11 @@ object SignalDatabaseMigrations {
|
|||
225 to V225_AddLocalUserJoinedStateAndGroupCallActiveState,
|
||||
226 to V226_AddAttachmentMediaIdIndex,
|
||||
227 to V227_AddAttachmentArchiveTransferState,
|
||||
228 to V228_AddNameCollisionTables
|
||||
228 to V228_AddNameCollisionTables,
|
||||
229 to V229_MarkMissedCallEventsNotified
|
||||
)
|
||||
|
||||
const val DATABASE_VERSION = 228
|
||||
const val DATABASE_VERSION = 229
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import net.zetetic.database.sqlcipher.SQLiteDatabase
|
||||
|
||||
/**
|
||||
* In order to both correct how we display missed calls and not spam users,
|
||||
* we want to mark every missed call event in the database as notified.
|
||||
*/
|
||||
@Suppress("ClassName")
|
||||
object V229_MarkMissedCallEventsNotified : SignalDatabaseMigration {
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL(
|
||||
"""
|
||||
UPDATE message
|
||||
SET notified = 1
|
||||
WHERE (type = 3) OR (type = 8)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -66,6 +66,25 @@ class CallLogEventSendJob private constructor(
|
|||
type = SyncMessage.CallLogEvent.Type.MARKED_AS_READ
|
||||
)
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@WorkerThread
|
||||
fun forMarkedAsReadInConversation(
|
||||
call: CallTable.Call
|
||||
) = CallLogEventSendJob(
|
||||
Parameters.Builder()
|
||||
.setQueue("CallLogEventSendJob")
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.build(),
|
||||
SyncMessage.CallLogEvent(
|
||||
timestamp = call.timestamp,
|
||||
callId = call.callId,
|
||||
conversationId = Recipient.resolved(call.peer).requireCallConversationId().toByteString(),
|
||||
type = SyncMessage.CallLogEvent.Type.MARKED_AS_READ_IN_CONVERSATION
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun serialize(): ByteArray = CallLogEventSendJobData.Builder()
|
||||
|
|
|
@ -1246,20 +1246,20 @@ object SyncMessageProcessor {
|
|||
|
||||
if (call != null) {
|
||||
log(envelopeTimestamp, "Synchronizing call log event with exact call data.")
|
||||
synchronizeCallLogEventViaTimestamp(envelopeTimestamp, callLogEvent.type, call.timestamp)
|
||||
synchronizeCallLogEventViaTimestamp(envelopeTimestamp, callLogEvent.type, call.timestamp, peer)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (timestamp != null) {
|
||||
warn(envelopeTimestamp, "Synchronize call log event using timestamp instead of exact values")
|
||||
synchronizeCallLogEventViaTimestamp(envelopeTimestamp, callLogEvent.type, timestamp)
|
||||
synchronizeCallLogEventViaTimestamp(envelopeTimestamp, callLogEvent.type, timestamp, peer)
|
||||
} else {
|
||||
log(envelopeTimestamp, "Failed to synchronize call log event, not enough information.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun synchronizeCallLogEventViaTimestamp(envelopeTimestamp: Long, eventType: CallLogEvent.Type?, timestamp: Long) {
|
||||
private fun synchronizeCallLogEventViaTimestamp(envelopeTimestamp: Long, eventType: CallLogEvent.Type?, timestamp: Long, peer: RecipientId?) {
|
||||
when (eventType) {
|
||||
CallLogEvent.Type.CLEAR -> {
|
||||
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(timestamp)
|
||||
|
@ -1270,6 +1270,15 @@ object SyncMessageProcessor {
|
|||
SignalDatabase.calls.markAllCallEventsRead(timestamp)
|
||||
}
|
||||
|
||||
CallLogEvent.Type.MARKED_AS_READ_IN_CONVERSATION -> {
|
||||
if (peer == null) {
|
||||
warn(envelopeTimestamp, "Cannot synchronize conversation calls, missing peer.")
|
||||
return
|
||||
}
|
||||
|
||||
SignalDatabase.calls.markAllCallEventsWithPeerBeforeTimestampRead(peer, timestamp)
|
||||
}
|
||||
|
||||
else -> log(envelopeTimestamp, "Synchronize call log event has an invalid type $eventType, ignoring.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,16 +12,17 @@ import com.annimon.stream.Stream;
|
|||
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.CallTable;
|
||||
import org.thoughtcrime.securesms.database.MessageTable.ExpirationInfo;
|
||||
import org.thoughtcrime.securesms.database.MessageTable.MarkedMessageInfo;
|
||||
import org.thoughtcrime.securesms.database.MessageTable.SyncMessageId;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.CallLogEventSendJob;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
|
||||
import org.thoughtcrime.securesms.notifications.v2.ConversationId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
|
@ -62,6 +63,7 @@ public class MarkReadReceiver extends BroadcastReceiver {
|
|||
}
|
||||
|
||||
process(messageIdsCollection);
|
||||
processCallEvents(threads, System.currentTimeMillis());
|
||||
|
||||
ApplicationDependencies.getMessageNotifier().updateNotification(context);
|
||||
finisher.finish();
|
||||
|
@ -102,6 +104,20 @@ public class MarkReadReceiver extends BroadcastReceiver {
|
|||
});
|
||||
}
|
||||
|
||||
public static void processCallEvents(@NonNull List<ConversationId> threads, long timestamp) {
|
||||
List<RecipientId> peers = SignalDatabase.threads().getRecipientIdsForThreadIds(threads.stream()
|
||||
.filter(it -> it.getGroupStoryId() == null)
|
||||
.map(ConversationId::getThreadId)
|
||||
.collect(java.util.stream.Collectors.toList()));
|
||||
|
||||
for (RecipientId peer : peers) {
|
||||
CallTable.Call lastCallInThread = SignalDatabase.calls().markAllCallEventsWithPeerBeforeTimestampRead(peer, timestamp);
|
||||
if (lastCallInThread != null) {
|
||||
ApplicationDependencies.getJobManager().add(CallLogEventSendJob.forMarkedAsReadInConversation(lastCallInThread));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void scheduleDeletion(@NonNull List<ExpirationInfo> expirationInfo) {
|
||||
if (expirationInfo.size() > 0) {
|
||||
long now = System.currentTimeMillis();
|
||||
|
|
|
@ -138,6 +138,7 @@ object NotificationStateProvider {
|
|||
) {
|
||||
private val isGroupStoryReply: Boolean = thread.groupStoryId != null
|
||||
private val isUnreadIncoming: Boolean = isUnreadMessage && !messageRecord.isOutgoing && !isGroupStoryReply
|
||||
private val isIncomingMissedCall: Boolean = !messageRecord.isOutgoing && (messageRecord.isMissedAudioCall || messageRecord.isMissedVideoCall)
|
||||
|
||||
private val isNotifiableGroupStoryMessage: Boolean =
|
||||
isUnreadMessage &&
|
||||
|
@ -146,7 +147,7 @@ object NotificationStateProvider {
|
|||
(isParentStorySentBySelf || messageRecord.hasSelfMention() || (hasSelfRepliedToStory && !messageRecord.isStoryReaction()))
|
||||
|
||||
fun includeMessage(notificationProfile: NotificationProfile?): MessageInclusion {
|
||||
return if (isUnreadIncoming || stickyThread || isNotifiableGroupStoryMessage) {
|
||||
return if (isUnreadIncoming || stickyThread || isNotifiableGroupStoryMessage || isIncomingMissedCall) {
|
||||
if (threadRecipient.isMuted && (threadRecipient.isDoNotNotifyMentions || !messageRecord.hasSelfMention())) {
|
||||
MessageInclusion.MUTE_FILTERED
|
||||
} else if (notificationProfile != null && !notificationProfile.isRecipientAllowed(threadRecipient.id) && !(notificationProfile.allowAllMentions && messageRecord.hasSelfMention())) {
|
||||
|
|
|
@ -637,8 +637,9 @@ message SyncMessage {
|
|||
|
||||
message CallLogEvent {
|
||||
enum Type {
|
||||
CLEAR = 0;
|
||||
MARKED_AS_READ = 1;
|
||||
CLEAR = 0;
|
||||
MARKED_AS_READ = 1;
|
||||
MARKED_AS_READ_IN_CONVERSATION = 2;
|
||||
}
|
||||
|
||||
optional Type type = 1;
|
||||
|
|
Loading…
Add table
Reference in a new issue