Add support for call log mark as read.
This commit is contained in:
parent
690608cdf3
commit
9f197b12ed
9 changed files with 128 additions and 29 deletions
|
@ -43,7 +43,8 @@ class CallLogRepository(
|
|||
|
||||
fun markAllCallEventsRead() {
|
||||
SignalExecutors.BOUNDED_IO.execute {
|
||||
SignalDatabase.messages.markAllCallEventsRead()
|
||||
SignalDatabase.calls.markAllCallEventsRead()
|
||||
ApplicationDependencies.getJobManager().add(CallLogEventSendJob.forMarkedAsRead(System.currentTimeMillis()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ 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.count
|
||||
import org.signal.core.util.delete
|
||||
import org.signal.core.util.deleteAll
|
||||
import org.signal.core.util.flatten
|
||||
|
@ -60,9 +61,10 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
const val TIMESTAMP = "timestamp"
|
||||
const val RINGER = "ringer"
|
||||
const val DELETION_TIMESTAMP = "deletion_timestamp"
|
||||
const val READ = "read"
|
||||
|
||||
//language=sql
|
||||
val CREATE_TABLE = """
|
||||
const val CREATE_TABLE = """
|
||||
CREATE TABLE $TABLE_NAME (
|
||||
$ID INTEGER PRIMARY KEY,
|
||||
$CALL_ID INTEGER NOT NULL,
|
||||
|
@ -74,6 +76,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
$TIMESTAMP INTEGER NOT NULL,
|
||||
$RINGER INTEGER DEFAULT NULL,
|
||||
$DELETION_TIMESTAMP INTEGER DEFAULT 0,
|
||||
$READ INTEGER DEFAULT 1,
|
||||
UNIQUE ($CALL_ID, $PEER) ON CONFLICT FAIL
|
||||
)
|
||||
"""
|
||||
|
@ -85,12 +88,29 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
)
|
||||
}
|
||||
|
||||
fun markAllCallEventsRead(timestamp: Long = Long.MAX_VALUE) {
|
||||
writableDatabase.update(TABLE_NAME)
|
||||
.values(READ to ReadState.serialize(ReadState.READ))
|
||||
.where("$TIMESTAMP <= ?", timestamp)
|
||||
.run()
|
||||
|
||||
notifyConversationListListeners()
|
||||
}
|
||||
|
||||
fun getUnreadMissedCallCount(): Long {
|
||||
return readableDatabase
|
||||
.count()
|
||||
.from(TABLE_NAME)
|
||||
.where("$EVENT = ? AND $READ = ?", Event.serialize(Event.MISSED), ReadState.serialize(ReadState.UNREAD))
|
||||
.run()
|
||||
.readToSingleLong()
|
||||
}
|
||||
|
||||
fun insertOneToOneCall(callId: Long, timestamp: Long, peer: RecipientId, type: Type, direction: Direction, event: Event) {
|
||||
val messageType: Long = Call.getMessageType(type, direction, event)
|
||||
|
||||
writableDatabase.withinTransaction {
|
||||
val result = SignalDatabase.messages.insertCallLog(peer, messageType, timestamp, direction == Direction.OUTGOING)
|
||||
|
||||
val values = contentValuesOf(
|
||||
CALL_ID to callId,
|
||||
MESSAGE_ID to result.messageId,
|
||||
|
@ -98,7 +118,8 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
TYPE to Type.serialize(type),
|
||||
DIRECTION to Direction.serialize(direction),
|
||||
EVENT to Event.serialize(event),
|
||||
TIMESTAMP to timestamp
|
||||
TIMESTAMP to timestamp,
|
||||
READ to ReadState.serialize(ReadState.UNREAD)
|
||||
)
|
||||
|
||||
writableDatabase.insert(TABLE_NAME, null, values)
|
||||
|
@ -114,7 +135,10 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
return writableDatabase.withinTransaction {
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(EVENT to Event.serialize(event))
|
||||
.values(
|
||||
EVENT to Event.serialize(event),
|
||||
READ to ReadState.serialize(ReadState.UNREAD)
|
||||
)
|
||||
.where("$CALL_ID = ?", callId)
|
||||
.run()
|
||||
|
||||
|
@ -1361,6 +1385,21 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
|
|||
}
|
||||
}
|
||||
|
||||
enum class ReadState(private val code: Int) {
|
||||
UNREAD(0),
|
||||
READ(1);
|
||||
|
||||
companion object Serializer : IntSerializer<ReadState> {
|
||||
override fun serialize(data: ReadState): Int {
|
||||
return data.code
|
||||
}
|
||||
|
||||
override fun deserialize(data: Int): ReadState {
|
||||
return ReadState.values().first { it.code == data }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class Event(private val code: Int) {
|
||||
/**
|
||||
* 1:1 Calls only.
|
||||
|
|
|
@ -774,7 +774,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
|||
}
|
||||
|
||||
fun insertCallLog(recipientId: RecipientId, type: Long, timestamp: Long, outgoing: Boolean): InsertResult {
|
||||
val unread = MessageTypes.isMissedAudioCall(type) || MessageTypes.isMissedVideoCall(type)
|
||||
val recipient = Recipient.resolved(recipientId)
|
||||
val threadIdResult = threads.getOrCreateThreadIdResultFor(recipient.id, recipient.isGroup)
|
||||
val threadId = threadIdResult.threadId
|
||||
|
@ -785,17 +784,13 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
|||
TO_RECIPIENT_ID to if (outgoing) recipientId.serialize() else Recipient.self().id.serialize(),
|
||||
DATE_RECEIVED to System.currentTimeMillis(),
|
||||
DATE_SENT to timestamp,
|
||||
READ to if (unread) 0 else 1,
|
||||
READ to 1,
|
||||
TYPE to type,
|
||||
THREAD_ID to threadId
|
||||
)
|
||||
|
||||
val messageId = writableDatabase.insert(TABLE_NAME, null, values)
|
||||
|
||||
if (unread) {
|
||||
threads.incrementUnread(threadId, 1, 0)
|
||||
}
|
||||
|
||||
threads.update(threadId, true)
|
||||
|
||||
notifyConversationListeners(threadId)
|
||||
|
@ -809,23 +804,17 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
|||
}
|
||||
|
||||
fun updateCallLog(messageId: Long, type: Long) {
|
||||
val unread = MessageTypes.isMissedAudioCall(type) || MessageTypes.isMissedVideoCall(type)
|
||||
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(
|
||||
TYPE to type,
|
||||
READ to if (unread) 0 else 1
|
||||
READ to 1
|
||||
)
|
||||
.where("$ID = ?", messageId)
|
||||
.run()
|
||||
|
||||
val threadId = getThreadIdForMessage(messageId)
|
||||
|
||||
if (unread) {
|
||||
threads.incrementUnread(threadId, 1, 0)
|
||||
}
|
||||
|
||||
threads.update(threadId, true)
|
||||
|
||||
notifyConversationListeners(threadId)
|
||||
|
@ -1281,7 +1270,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
|||
return MmsReader(rawQueryWithAttachments(query, args, false, limit.toLong()))
|
||||
}
|
||||
|
||||
fun getUnreadMisedCallCount(): Long {
|
||||
fun getUnreadMissedCallCount(): Long {
|
||||
return readableDatabase
|
||||
.select("COUNT(*)")
|
||||
.from(TABLE_NAME)
|
||||
|
|
|
@ -78,6 +78,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V217_MessageTableEx
|
|||
import org.thoughtcrime.securesms.database.helpers.migration.V218_RecipientPniSignatureVerified
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V219_PniPreKeyStores
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V220_PreKeyConstraints
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V221_AddReadColumnToCallEventsTable
|
||||
|
||||
/**
|
||||
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
|
||||
|
@ -158,10 +159,11 @@ object SignalDatabaseMigrations {
|
|||
217 to V217_MessageTableExtrasColumn,
|
||||
218 to V218_RecipientPniSignatureVerified,
|
||||
219 to V219_PniPreKeyStores,
|
||||
220 to V220_PreKeyConstraints
|
||||
220 to V220_PreKeyConstraints,
|
||||
221 to V221_AddReadColumnToCallEventsTable
|
||||
)
|
||||
|
||||
const val DATABASE_VERSION = 220
|
||||
const val DATABASE_VERSION = 221
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Adds read state to call events to separately track from the primary messages table.
|
||||
* Copies the current read state in from the message database, and then clears the message
|
||||
* database 'read' flag as well as decrements the unread count in the thread databse.
|
||||
*/
|
||||
@Suppress("ClassName")
|
||||
object V221_AddReadColumnToCallEventsTable : SignalDatabaseMigration {
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL("ALTER TABLE call ADD COLUMN read INTEGER DEFAULT 1")
|
||||
|
||||
db.execSQL(
|
||||
"""
|
||||
UPDATE call
|
||||
SET read = (SELECT read FROM message WHERE _id = call.message_id)
|
||||
WHERE event = 3 AND direction = 0
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
db.execSQL(
|
||||
"""
|
||||
UPDATE thread
|
||||
SET unread_count = thread.unread_count - 1
|
||||
WHERE _id IN (SELECT thread_id FROM message WHERE (type = 3 OR type = 8) AND read = 0) AND unread_count > 0
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
db.execSQL(
|
||||
"""
|
||||
UPDATE message
|
||||
SET read = 1
|
||||
WHERE (type = 3 OR type = 8) AND read = 0
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -41,6 +41,21 @@ class CallLogEventSendJob private constructor(
|
|||
type = SyncMessage.CallLogEvent.Type.CLEAR
|
||||
)
|
||||
)
|
||||
|
||||
fun forMarkedAsRead(
|
||||
timestamp: Long
|
||||
) = CallLogEventSendJob(
|
||||
Parameters.Builder()
|
||||
.setQueue("CallLogEventSendJob")
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.build(),
|
||||
SyncMessage.CallLogEvent(
|
||||
timestamp = timestamp,
|
||||
type = SyncMessage.CallLogEvent.Type.MARKED_AS_READ
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun serialize(): ByteArray = CallLogEventSendJobData.Builder()
|
||||
|
|
|
@ -1197,16 +1197,23 @@ object SyncMessageProcessor {
|
|||
}
|
||||
|
||||
private fun handleSynchronizeCallLogEvent(callLogEvent: CallLogEvent, envelopeTimestamp: Long) {
|
||||
if (callLogEvent.type != CallLogEvent.Type.CLEAR) {
|
||||
log(envelopeTimestamp, "Synchronize call log event has an invalid type ${callLogEvent.type}, ignoring.")
|
||||
return
|
||||
} else if (callLogEvent.timestamp == null) {
|
||||
if (callLogEvent.timestamp == null) {
|
||||
log(envelopeTimestamp, "Synchronize call log event has null timestamp")
|
||||
return
|
||||
}
|
||||
|
||||
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(callLogEvent.timestamp!!)
|
||||
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(callLogEvent.timestamp!!)
|
||||
when (callLogEvent.type) {
|
||||
CallLogEvent.Type.CLEAR -> {
|
||||
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(callLogEvent.timestamp!!)
|
||||
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(callLogEvent.timestamp!!)
|
||||
}
|
||||
|
||||
CallLogEvent.Type.MARKED_AS_READ -> {
|
||||
SignalDatabase.calls.markAllCallEventsRead(callLogEvent.timestamp!!)
|
||||
}
|
||||
|
||||
else -> log(envelopeTimestamp, "Synchronize call log event has an invalid type ${callLogEvent.type}, ignoring.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSynchronizeCallLink(callLinkUpdate: CallLinkUpdate, envelopeTimestamp: Long) {
|
||||
|
|
|
@ -28,6 +28,6 @@ class ConversationListTabRepository {
|
|||
}
|
||||
|
||||
fun getNumberOfUnseenCalls(): Flowable<Long> {
|
||||
return RxDatabaseObserver.conversationList.map { SignalDatabase.messages.getUnreadMisedCallCount() }
|
||||
return RxDatabaseObserver.conversationList.map { SignalDatabase.calls.getUnreadMissedCallCount() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -633,7 +633,8 @@ message SyncMessage {
|
|||
|
||||
message CallLogEvent {
|
||||
enum Type {
|
||||
CLEAR = 0;
|
||||
CLEAR = 0;
|
||||
MARKED_AS_READ = 1;
|
||||
}
|
||||
|
||||
optional Type type = 1;
|
||||
|
|
Loading…
Add table
Reference in a new issue