Improve call tab performance.

This commit is contained in:
Alex Hart
2025-01-16 17:19:55 -04:00
committed by Greyson Parrelli
parent 71c21eeba6
commit 0b24e42448
9 changed files with 634 additions and 315 deletions

View File

@@ -13,7 +13,6 @@ import org.signal.core.util.deleteAll
import org.signal.core.util.exists
import org.signal.core.util.flatten
import org.signal.core.util.insertInto
import org.signal.core.util.isAbsent
import org.signal.core.util.logging.Log
import org.signal.core.util.readToList
import org.signal.core.util.readToMap
@@ -23,7 +22,6 @@ import org.signal.core.util.requireBoolean
import org.signal.core.util.requireLong
import org.signal.core.util.requireNonNullString
import org.signal.core.util.requireObject
import org.signal.core.util.requireString
import org.signal.core.util.select
import org.signal.core.util.toInt
import org.signal.core.util.toSingleLine
@@ -31,9 +29,6 @@ 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.GroupCallUpdateDetailsUtil
import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobs.CallLinkUpdateSendJob
@@ -238,7 +233,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
.readToSingleObject(Call.Deserializer)
}
fun getCalls(messageIds: Collection<Long>): Map<Long, Call> {
fun getCallsForCache(messageIds: Collection<Long>): Map<Long, Call> {
val queries = SqlUtil.buildCollectionQuery(MESSAGE_ID, messageIds)
val maps = queries.map { query ->
readableDatabase
@@ -1242,180 +1237,6 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
// endregion
private fun getCallsCursor(isCount: Boolean, offset: Int, limit: Int, searchTerm: String?, filter: CallLogFilter): Cursor {
val isMissedGenericGroupCall = "$EVENT = ${Event.serialize(Event.GENERIC_GROUP_CALL)} AND $LOCAL_JOINED = ${false.toInt()} AND $GROUP_CALL_ACTIVE = ${false.toInt()}"
val filterClause: SqlUtil.Query = when (filter) {
CallLogFilter.ALL -> SqlUtil.buildQuery("$DELETION_TIMESTAMP = 0")
CallLogFilter.MISSED -> SqlUtil.buildQuery("$TYPE != ${Type.serialize(Type.AD_HOC_CALL)} AND $DIRECTION == ${Direction.serialize(Direction.INCOMING)} AND ($EVENT = ${Event.serialize(Event.MISSED)} OR $EVENT = ${Event.serialize(Event.MISSED_NOTIFICATION_PROFILE)} OR $EVENT = ${Event.serialize(Event.NOT_ACCEPTED)} OR $EVENT = ${Event.serialize(Event.DECLINED)} OR ($isMissedGenericGroupCall)) AND $DELETION_TIMESTAMP = 0")
CallLogFilter.AD_HOC -> SqlUtil.buildQuery("$TYPE = ${Type.serialize(Type.AD_HOC_CALL)} AND $DELETION_TIMESTAMP = 0")
}
val queryClause: SqlUtil.Query = if (!searchTerm.isNullOrEmpty()) {
val glob = SqlUtil.buildCaseInsensitiveGlobPattern(searchTerm)
val selection =
"""
${RecipientTable.TABLE_NAME}.${RecipientTable.BLOCKED} = ? AND ${RecipientTable.TABLE_NAME}.${RecipientTable.HIDDEN} = ? AND
(
sort_name GLOB ? OR
${RecipientTable.TABLE_NAME}.${RecipientTable.USERNAME} GLOB ? OR
${RecipientTable.TABLE_NAME}.${RecipientTable.E164} GLOB ? OR
${RecipientTable.TABLE_NAME}.${RecipientTable.EMAIL} GLOB ?
)
"""
SqlUtil.buildQuery(selection, 0, 0, glob, glob, glob, glob)
} else {
SqlUtil.buildQuery(
"""
${RecipientTable.TABLE_NAME}.${RecipientTable.BLOCKED} = ? AND ${RecipientTable.TABLE_NAME}.${RecipientTable.HIDDEN} = ?
""",
0,
0
)
}
val offsetLimit = if (limit > 0) {
"LIMIT $offset,$limit"
} else {
""
}
val projection = if (isCount) {
"COUNT(*) OVER() as count"
} else {
"p.$ID, p.$TIMESTAMP, $EVENT, $DIRECTION, $PEER, p.$TYPE, $CALL_ID, $MESSAGE_ID, $RINGER, $LOCAL_JOINED, $GROUP_CALL_ACTIVE, children, in_period, ${MessageTable.BODY}"
}
val recipientSearchProjection = if (searchTerm.isNullOrEmpty()) {
""
} else {
"""
,LOWER(
COALESCE(
NULLIF(${GroupTable.TABLE_NAME}.${GroupTable.TITLE}, ''),
NULLIF(${RecipientTable.TABLE_NAME}.${RecipientTable.NICKNAME_JOINED_NAME}, ''),
NULLIF(${RecipientTable.TABLE_NAME}.${RecipientTable.NICKNAME_GIVEN_NAME}, ''),
NULLIF(${RecipientTable.TABLE_NAME}.${RecipientTable.SYSTEM_JOINED_NAME}, ''),
NULLIF(${RecipientTable.TABLE_NAME}.${RecipientTable.SYSTEM_GIVEN_NAME}, ''),
NULLIF(${RecipientTable.TABLE_NAME}.${RecipientTable.PROFILE_JOINED_NAME}, ''),
NULLIF(${RecipientTable.TABLE_NAME}.${RecipientTable.PROFILE_GIVEN_NAME}, ''),
NULLIF(${RecipientTable.TABLE_NAME}.${RecipientTable.USERNAME}, '')
)
) AS sort_name
""".trimIndent()
}
val join = if (isCount) {
""
} else {
"LEFT JOIN ${MessageTable.TABLE_NAME} ON ${MessageTable.TABLE_NAME}.${MessageTable.ID} = $MESSAGE_ID"
}
// Group call events by those we consider missed or not missed to build out our call log aggregation.
val eventTypeSubQuery = """
($TABLE_NAME.$EVENT = c.$EVENT AND (
$TABLE_NAME.$EVENT = ${Event.serialize(Event.MISSED)} OR
$TABLE_NAME.$EVENT = ${Event.serialize(Event.MISSED_NOTIFICATION_PROFILE)} OR
$TABLE_NAME.$EVENT = ${Event.serialize(Event.NOT_ACCEPTED)} OR
$TABLE_NAME.$EVENT = ${Event.serialize(Event.DECLINED)} OR
($TABLE_NAME.$isMissedGenericGroupCall)
)) OR (
$TABLE_NAME.$EVENT != ${Event.serialize(Event.MISSED)} AND
c.$EVENT != ${Event.serialize(Event.MISSED)} AND
$TABLE_NAME.$EVENT != ${Event.serialize(Event.MISSED_NOTIFICATION_PROFILE)} AND
c.$EVENT != ${Event.serialize(Event.MISSED_NOTIFICATION_PROFILE)} AND
$TABLE_NAME.$EVENT != ${Event.serialize(Event.NOT_ACCEPTED)} AND
c.$EVENT != ${Event.serialize(Event.NOT_ACCEPTED)} AND
$TABLE_NAME.$EVENT != ${Event.serialize(Event.DECLINED)} AND
c.$EVENT != ${Event.serialize(Event.DECLINED)} AND
(NOT ($TABLE_NAME.$isMissedGenericGroupCall)) AND
(NOT (c.$isMissedGenericGroupCall))
)
"""
//language=sql
val statement = """
SELECT $projection
$recipientSearchProjection
FROM (
WITH cte AS (
SELECT
$ID, $TIMESTAMP, $EVENT, $DIRECTION, $PEER, $TYPE, $CALL_ID, $MESSAGE_ID, $RINGER, $LOCAL_JOINED, $GROUP_CALL_ACTIVE,
(
SELECT
$ID
FROM
$TABLE_NAME
WHERE
$TABLE_NAME.$DIRECTION = c.$DIRECTION
AND $TABLE_NAME.$PEER = c.$PEER
AND $TABLE_NAME.$TIMESTAMP - $TIME_WINDOW <= c.$TIMESTAMP
AND $TABLE_NAME.$TIMESTAMP >= c.$TIMESTAMP
AND ($eventTypeSubQuery)
AND ${filterClause.where}
ORDER BY
$TIMESTAMP DESC
) as parent,
(
SELECT
group_concat($ID)
FROM
$TABLE_NAME
WHERE
$TABLE_NAME.$DIRECTION = c.$DIRECTION
AND $TABLE_NAME.$PEER = c.$PEER
AND c.$TIMESTAMP - $TIME_WINDOW <= $TABLE_NAME.$TIMESTAMP
AND c.$TIMESTAMP >= $TABLE_NAME.$TIMESTAMP
AND ($eventTypeSubQuery)
AND ${filterClause.where}
) as children,
(
SELECT
group_concat($ID)
FROM
$TABLE_NAME
WHERE
c.$TIMESTAMP - $TIME_WINDOW <= $TABLE_NAME.$TIMESTAMP
AND c.$TIMESTAMP >= $TABLE_NAME.$TIMESTAMP
AND ${filterClause.where}
) as in_period
FROM
$TABLE_NAME c INDEXED BY $CALL_LOG_INDEX
WHERE ${filterClause.where}
ORDER BY
$TIMESTAMP DESC
)
SELECT
*,
CASE
WHEN LAG (parent, 1, 0) OVER (
ORDER BY
$TIMESTAMP DESC
) != parent THEN $ID
ELSE parent
END true_parent
FROM
cte
) p
INNER JOIN ${RecipientTable.TABLE_NAME} ON ${RecipientTable.TABLE_NAME}.${RecipientTable.ID} = $PEER
$join
LEFT JOIN ${GroupTable.TABLE_NAME} ON ${GroupTable.TABLE_NAME}.${GroupTable.RECIPIENT_ID} = ${RecipientTable.TABLE_NAME}.${RecipientTable.ID}
WHERE true_parent = p.$ID
AND CASE
WHEN p.$TYPE = ${Type.serialize(Type.AD_HOC_CALL)} THEN EXISTS (SELECT * FROM ${CallLinkTable.TABLE_NAME} WHERE ${CallLinkTable.RECIPIENT_ID} = $PEER AND ${CallLinkTable.ROOT_KEY} NOT NULL)
ELSE 1
END
${if (queryClause.where.isNotEmpty()) "AND ${queryClause.where}" else ""}
GROUP BY CASE WHEN p.type = 4 THEN p.peer ELSE p._id END
ORDER BY p.$TIMESTAMP DESC
$offsetLimit
"""
return readableDatabase.query(
statement,
queryClause.whereArgs
)
}
fun getLatestRingingCalls(): List<Call> {
return readableDatabase.select()
.from(TABLE_NAME)
@@ -1445,56 +1266,20 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
}
}
fun getCallsCount(searchTerm: String?, filter: CallLogFilter): Int {
return getCallsCursor(true, 0, 1, searchTerm, filter).use {
if (it.moveToFirst()) {
it.getInt(0)
} else {
0
}
}
}
fun getCalls(offset: Int, limit: Int, searchTerm: String?, filter: CallLogFilter): List<CallLogRow.Call> {
return getCallsCursor(false, offset, limit, searchTerm, filter).readToList { cursor ->
val call = Call.deserialize(cursor)
val groupCallDetails = GroupCallUpdateDetailsUtil.parse(cursor.requireString(MessageTable.BODY))
val children = cursor.requireNonNullString("children")
.split(',')
.map { it.toLong() }
.toSet()
val inPeriod = cursor.requireNonNullString("in_period")
.split(',')
.map { it.toLong() }
.sortedDescending()
.toSet()
val actualChildren = inPeriod.takeWhile { children.contains(it) }
val peer = Recipient.resolved(call.peer)
val canUserBeginCall = if (peer.isGroup) {
val record = SignalDatabase.groups.getGroup(peer.id)
!record.isAbsent() &&
record.get().isActive &&
(!record.get().isAnnouncementGroup || record.get().memberLevel(Recipient.self()) == GroupTable.MemberLevel.ADMINISTRATOR)
} else {
true
}
CallLogRow.Call(
record = call,
date = call.timestamp,
peer = peer,
groupCallState = CallLogRow.GroupCallState.fromDetails(groupCallDetails),
children = actualChildren.toSet(),
searchQuery = searchTerm,
callLinkPeekInfo = AppDependencies.signalCallManager.peekInfoSnapshot[peer.id],
canUserBeginCall = canUserBeginCall
fun getCallsForCache(limit: Int): Cursor {
return readableDatabase
.query(
"""
SELECT $TABLE_NAME.*, ${MessageTable.TABLE_NAME}.${MessageTable.BODY}, ${GroupTable.TABLE_NAME}.${GroupTable.V2_DECRYPTED_GROUP}
FROM $TABLE_NAME
INNER JOIN ${RecipientTable.TABLE_NAME} ON ${RecipientTable.TABLE_NAME}.${RecipientTable.ID} = $PEER
LEFT JOIN ${GroupTable.TABLE_NAME} ON ${GroupTable.TABLE_NAME}.${GroupTable.RECIPIENT_ID} = $PEER
LEFT JOIN ${MessageTable.TABLE_NAME} ON ${MessageTable.TABLE_NAME}.${MessageTable.ID} = $MESSAGE_ID
WHERE ${RecipientTable.TABLE_NAME}.${RecipientTable.BLOCKED} = 0 AND ${RecipientTable.TABLE_NAME}.${RecipientTable.HIDDEN} = 0 AND $TABLE_NAME.$EVENT != ${Event.DELETE.code}
ORDER BY $TIMESTAMP DESC
LIMIT $limit
""".trimIndent()
)
}
}
override fun remapRecipient(fromId: RecipientId, toId: RecipientId) {
@@ -1579,7 +1364,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
}
}
enum class Type(private val code: Int) {
enum class Type(val code: Int) {
AUDIO_CALL(0),
VIDEO_CALL(1),
GROUP_CALL(3),
@@ -1611,7 +1396,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
}
}
enum class Direction(private val code: Int) {
enum class Direction(val code: Int) {
INCOMING(0),
OUTGOING(1);
@@ -1652,7 +1437,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
}
}
enum class Event(private val code: Int) {
enum class Event(val code: Int) {
/**
* 1:1 Calls only.
*/