refactor: message logger

- send timestamp
- sender/conversation usernames
This commit is contained in:
rhunk 2024-06-02 10:12:46 +02:00
parent cb51da8166
commit 6d3e5ed79c
5 changed files with 181 additions and 102 deletions

View File

@ -20,6 +20,7 @@ import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavBackStackEntry import androidx.navigation.NavBackStackEntry
@ -28,6 +29,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import me.rhunk.snapenhance.bridge.DownloadCallback import me.rhunk.snapenhance.bridge.DownloadCallback
import me.rhunk.snapenhance.common.bridge.wrapper.ConversationInfo
import me.rhunk.snapenhance.common.bridge.wrapper.LoggedMessage import me.rhunk.snapenhance.common.bridge.wrapper.LoggedMessage
import me.rhunk.snapenhance.common.bridge.wrapper.LoggerWrapper import me.rhunk.snapenhance.common.bridge.wrapper.LoggerWrapper
import me.rhunk.snapenhance.common.data.ContentType import me.rhunk.snapenhance.common.data.ContentType
@ -43,12 +45,8 @@ import me.rhunk.snapenhance.core.features.impl.downloader.decoder.DecodedAttachm
import me.rhunk.snapenhance.core.features.impl.downloader.decoder.MessageDecoder import me.rhunk.snapenhance.core.features.impl.downloader.decoder.MessageDecoder
import me.rhunk.snapenhance.download.DownloadProcessor import me.rhunk.snapenhance.download.DownloadProcessor
import me.rhunk.snapenhance.storage.findFriend import me.rhunk.snapenhance.storage.findFriend
import me.rhunk.snapenhance.storage.getFriendInfo
import me.rhunk.snapenhance.storage.getGroupInfo
import me.rhunk.snapenhance.ui.manager.Routes import me.rhunk.snapenhance.ui.manager.Routes
import java.nio.ByteBuffer
import java.text.DateFormat import java.text.DateFormat
import java.util.UUID
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@ -58,15 +56,12 @@ class LoggerHistoryRoot : Routes.Route() {
private var stringFilter by mutableStateOf("") private var stringFilter by mutableStateOf("")
private var reverseOrder by mutableStateOf(true) private var reverseOrder by mutableStateOf(true)
private inline fun decodeMessage(message: LoggedMessage, result: (senderId: String?, contentType: ContentType, messageReader: ProtoReader, attachments: List<DecodedAttachment>) -> Unit) { private inline fun decodeMessage(message: LoggedMessage, result: (contentType: ContentType, messageReader: ProtoReader, attachments: List<DecodedAttachment>) -> Unit) {
runCatching { runCatching {
val messageObject = JsonParser.parseString(String(message.messageData, Charsets.UTF_8)).asJsonObject val messageObject = JsonParser.parseString(String(message.messageData, Charsets.UTF_8)).asJsonObject
val senderId = messageObject.getAsJsonObject("mSenderId")?.getAsJsonArray("mId")?.map { it.asByte }?.toByteArray()?.let {
ByteBuffer.wrap(it).run { UUID(long, long) }.toString()
}
val messageContent = messageObject.getAsJsonObject("mMessageContent") val messageContent = messageObject.getAsJsonObject("mMessageContent")
val messageReader = messageContent.getAsJsonArray("mContent").map { it.asByte }.toByteArray().let { ProtoReader(it) } val messageReader = messageContent.getAsJsonArray("mContent").map { it.asByte }.toByteArray().let { ProtoReader(it) }
result(senderId, ContentType.fromMessageContainer(messageReader) ?: ContentType.UNKNOWN, messageReader, MessageDecoder.decode(messageContent)) result(ContentType.fromMessageContainer(messageReader) ?: ContentType.UNKNOWN, messageReader, MessageDecoder.decode(messageContent))
}.onFailure { }.onFailure {
context.log.error("Failed to decode message", it) context.log.error("Failed to decode message", it)
} }
@ -129,12 +124,10 @@ class LoggerHistoryRoot : Routes.Route() {
LaunchedEffect(Unit, message) { LaunchedEffect(Unit, message) {
runCatching { runCatching {
decodeMessage(message) { senderId, contentType, messageReader, attachments -> decodeMessage(message) { contentType, messageReader, attachments ->
val senderUsername = senderId?.let { context.database.getFriendInfo(it)?.mutableUsername } ?: translation["unknown_sender"]
@Composable @Composable
fun ContentHeader() { fun ContentHeader() {
Text("$senderUsername (${contentType.toString().lowercase()})", modifier = Modifier.padding(end = 4.dp), fontWeight = FontWeight.ExtraLight) Text("${message.username} (${contentType.toString().lowercase()}) - ${DateFormat.getDateTimeInstance().format(message.sendTimestamp)}", modifier = Modifier.padding(end = 4.dp), fontWeight = FontWeight.ExtraLight)
} }
if (contentType == ContentType.CHAT) { if (contentType == ContentType.CHAT) {
@ -187,7 +180,7 @@ class LoggerHistoryRoot : Routes.Route() {
ElevatedButton(onClick = { ElevatedButton(onClick = {
context.coroutineScope.launch { context.coroutineScope.launch {
runCatching { runCatching {
downloadAttachment(message.timestamp, attachment) downloadAttachment(message.sendTimestamp, attachment)
}.onFailure { }.onFailure {
context.log.error("Failed to download attachment", it) context.log.error("Failed to download attachment", it)
context.shortToast(translation["download_attachment_failed_toast"]) context.shortToast(translation["download_attachment_failed_toast"])
@ -232,17 +225,26 @@ class LoggerHistoryRoot : Routes.Route() {
expanded = expanded, expanded = expanded,
onExpandedChange = { expanded = it }, onExpandedChange = { expanded = it },
) { ) {
fun formatConversationId(conversationId: String?): String? { fun formatConversationInfo(conversationInfo: ConversationInfo?): String? {
if (conversationId == null) return null if (conversationInfo == null) return null
return context.database.getGroupInfo(conversationId)?.name?.let {
return conversationInfo.groupTitle?.let {
translation.format("list_group_format", "name" to it) translation.format("list_group_format", "name" to it)
} ?: context.database.findFriend(conversationId)?.let { } ?: conversationInfo.usernames.takeIf { it.size > 1 }?.let {
translation.format("list_friend_format", "name" to (it.displayName?.let { name -> "$name (${it.mutableUsername})" } ?: it.mutableUsername)) translation.format("list_friend_format", "name" to ("(" + it.joinToString(", ") + ")"))
} ?: conversationId } ?: context.database.findFriend(conversationInfo.conversationId)?.let {
translation.format("list_friend_format", "name" to "(" + (conversationInfo.usernames + listOf(it.mutableUsername)).toSet().joinToString(", ") + ")")
} ?: conversationInfo.usernames.firstOrNull()?.let {
translation.format("list_friend_format", "name" to "($it)")
}
}
val selectedConversationInfo by rememberAsyncMutableState(defaultValue = null, keys = arrayOf(selectedConversation)) {
selectedConversation?.let { loggerWrapper.getConversationInfo(it) }
} }
OutlinedTextField( OutlinedTextField(
value = remember(selectedConversation) { formatConversationId(selectedConversation) ?: "Select a conversation" }, value = remember(selectedConversationInfo) { formatConversationInfo(selectedConversationInfo) ?: "Select a conversation" },
onValueChange = {}, onValueChange = {},
readOnly = true, readOnly = true,
modifier = Modifier modifier = Modifier
@ -260,7 +262,15 @@ class LoggerHistoryRoot : Routes.Route() {
selectedConversation = conversationId selectedConversation = conversationId
expanded = false expanded = false
}, text = { }, text = {
Text(remember(conversationId) { formatConversationId(conversationId) ?: "Unknown conversation" }) val conversationInfo by rememberAsyncMutableState(defaultValue = null, keys = arrayOf(conversationId)) {
formatConversationInfo(loggerWrapper.getConversationInfo(conversationId))
}
Text(
text = remember(conversationInfo) { conversationInfo ?: conversationId },
fontWeight = if (conversationId == selectedConversation) FontWeight.Bold else FontWeight.Normal,
overflow = TextOverflow.Ellipsis
)
}) })
} }
} }
@ -320,7 +330,7 @@ class LoggerHistoryRoot : Routes.Route() {
) { messageData -> ) { messageData ->
if (stringFilter.isEmpty()) return@fetchMessages true if (stringFilter.isEmpty()) return@fetchMessages true
var isMatch = false var isMatch = false
decodeMessage(messageData) { _, contentType, messageReader, _ -> decodeMessage(messageData) { contentType, messageReader, _ ->
if (contentType == ContentType.CHAT) { if (contentType == ContentType.CHAT) {
val content = messageReader.getString(2, 1) ?: return@decodeMessage val content = messageReader.getString(2, 1) ?: return@decodeMessage
isMatch = content.contains(stringFilter, ignoreCase = true) isMatch = content.contains(stringFilter, ignoreCase = true)
@ -332,7 +342,7 @@ class LoggerHistoryRoot : Routes.Route() {
hasReachedEnd = true hasReachedEnd = true
return@withContext return@withContext
} }
lastFetchMessageTimestamp = newMessages.lastOrNull()?.timestamp ?: return@withContext lastFetchMessageTimestamp = newMessages.lastOrNull()?.sendTimestamp ?: return@withContext
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
messages.addAll(newMessages) messages.addAll(newMessages)
} }

View File

@ -0,0 +1,11 @@
package me.rhunk.snapenhance.bridge.logger;
parcelable BridgeLoggedMessage {
long messageId;
String conversationId;
String userId;
String username;
long sendTimestamp;
@nullable String groupTitle;
byte[] messageData;
}

View File

@ -1,5 +1,7 @@
package me.rhunk.snapenhance.bridge.logger; package me.rhunk.snapenhance.bridge.logger;
import me.rhunk.snapenhance.bridge.logger.BridgeLoggedMessage;
interface LoggerInterface { interface LoggerInterface {
/** /**
* Get the ids of the messages that are logged * Get the ids of the messages that are logged
@ -15,7 +17,7 @@ interface LoggerInterface {
/** /**
* Add a message to the message logger database if it is not already there * Add a message to the message logger database if it is not already there
*/ */
oneway void addMessage(String conversationId, long id, in byte[] message); oneway void addMessage(in BridgeLoggedMessage message);
/** /**
* Delete a message from the message logger database * Delete a message from the message logger database

View File

@ -6,6 +6,7 @@ import android.database.sqlite.SQLiteDatabase
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.JsonObject import com.google.gson.JsonObject
import kotlinx.coroutines.* import kotlinx.coroutines.*
import me.rhunk.snapenhance.bridge.logger.BridgeLoggedMessage
import me.rhunk.snapenhance.bridge.logger.LoggerInterface import me.rhunk.snapenhance.bridge.logger.LoggerInterface
import me.rhunk.snapenhance.common.bridge.InternalFileHandleType import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
import me.rhunk.snapenhance.common.data.StoryData import me.rhunk.snapenhance.common.data.StoryData
@ -26,10 +27,22 @@ class LoggedMessageEdit(
class LoggedMessage( class LoggedMessage(
val messageId: Long, val messageId: Long,
val timestamp: Long, val conversationId: String,
val userId: String,
val username: String,
val sendTimestamp: Long,
val addedTimestamp: Long,
val groupTitle: String?,
val messageData: ByteArray, val messageData: ByteArray,
) )
class ConversationInfo(
val conversationId: String,
val participantSize: Int,
val groupTitle: String?,
val usernames: List<String>
)
class TrackerLog( class TrackerLog(
val id: Int, val id: Int,
val timestamp: Long, val timestamp: Long,
@ -77,9 +90,13 @@ class LoggerWrapper(
SQLiteDatabaseHelper.createTablesFromSchema(openedDatabase, mapOf( SQLiteDatabaseHelper.createTablesFromSchema(openedDatabase, mapOf(
"messages" to listOf( "messages" to listOf(
"id INTEGER PRIMARY KEY", "id INTEGER PRIMARY KEY",
"added_timestamp BIGINT",
"conversation_id VARCHAR",
"message_id BIGINT", "message_id BIGINT",
"conversation_id VARCHAR",
"user_id CHAR(36)",
"username VARCHAR",
"send_timestamp BIGINT",
"added_timestamp BIGINT",
"group_title VARCHAR",
"message_data BLOB" "message_data BLOB"
), ),
"chat_edits" to listOf( "chat_edits" to listOf(
@ -150,61 +167,62 @@ class LoggerWrapper(
} }
} }
override fun addMessage(conversationId: String, messageId: Long, serializedMessage: ByteArray) { override fun addMessage(bridgeLoggedMessage: BridgeLoggedMessage) {
val hasMessage = database.rawQuery("SELECT message_id FROM messages WHERE conversation_id = ? AND message_id = ?", arrayOf(conversationId, messageId.toString())).use { val hasMessage = database.rawQuery("SELECT message_id FROM messages WHERE conversation_id = ? AND message_id = ?", arrayOf(bridgeLoggedMessage.conversationId, bridgeLoggedMessage.messageId.toString())).use {
it.moveToFirst() it.moveToFirst()
it.count > 0 it.count > 0
} }
if (!hasMessage) { if (!hasMessage) {
runBlocking { runBlocking(coroutineScope.coroutineContext) {
withContext(coroutineScope.coroutineContext) {
database.insert("messages", null, ContentValues().apply { database.insert("messages", null, ContentValues().apply {
put("message_id", bridgeLoggedMessage.messageId)
put("conversation_id", bridgeLoggedMessage.conversationId)
put("user_id", bridgeLoggedMessage.userId)
put("username", bridgeLoggedMessage.username)
put("send_timestamp", bridgeLoggedMessage.sendTimestamp)
put("added_timestamp", System.currentTimeMillis()) put("added_timestamp", System.currentTimeMillis())
put("conversation_id", conversationId) put("group_title", bridgeLoggedMessage.groupTitle)
put("message_id", messageId) put("message_data", bridgeLoggedMessage.messageData)
put("message_data", serializedMessage)
}) })
} }
} }
}
// handle message edits // handle message edits
runBlocking { runBlocking(coroutineScope.coroutineContext) {
withContext(coroutineScope.coroutineContext) {
runCatching { runCatching {
val messageObject = gson.fromJson( val messageObject = gson.fromJson(
serializedMessage.toString(Charsets.UTF_8), bridgeLoggedMessage.messageData.toString(Charsets.UTF_8),
JsonObject::class.java JsonObject::class.java
) )
if (messageObject.getAsJsonObject("mMessageContent") if (messageObject.getAsJsonObject("mMessageContent")
?.getAsJsonPrimitive("mContentType")?.asString != "CHAT" ?.getAsJsonPrimitive("mContentType")?.asString != "CHAT"
) return@withContext ) return@runBlocking
val metadata = messageObject.getAsJsonObject("mMetadata") val metadata = messageObject.getAsJsonObject("mMetadata")
if (metadata.get("mIsEdited")?.asBoolean != true) return@withContext if (metadata.get("mIsEdited")?.asBoolean != true) return@runBlocking
val messageTextContent = val messageTextContent =
messageObject.getAsJsonObject("mMessageContent")?.getAsJsonArray("mContent") messageObject.getAsJsonObject("mMessageContent")?.getAsJsonArray("mContent")
?.map { it.asByte }?.toByteArray()?.let { ?.map { it.asByte }?.toByteArray()?.let {
ProtoReader(it).getString(2, 1) ProtoReader(it).getString(2, 1)
} ?: return@withContext } ?: return@runBlocking
database.rawQuery( database.rawQuery(
"SELECT MAX(edit_number), message_text FROM chat_edits WHERE conversation_id = ? AND message_id = ?", "SELECT MAX(edit_number), message_text FROM chat_edits WHERE conversation_id = ? AND message_id = ?",
arrayOf(conversationId, messageId.toString()) arrayOf(bridgeLoggedMessage.conversationId, bridgeLoggedMessage.messageId.toString())
).use { ).use {
it.moveToFirst() it.moveToFirst()
val editNumber = it.getInt(0) val editNumber = it.getInt(0)
val lastEditedMessage = it.getString(1) val lastEditedMessage = it.getString(1)
if (lastEditedMessage == messageTextContent) return@withContext if (lastEditedMessage == messageTextContent) return@runBlocking
database.insert("chat_edits", null, ContentValues().apply { database.insert("chat_edits", null, ContentValues().apply {
put("edit_number", editNumber + 1) put("edit_number", editNumber + 1)
put("added_timestamp", System.currentTimeMillis()) put("added_timestamp", System.currentTimeMillis())
put("conversation_id", conversationId) put("conversation_id", bridgeLoggedMessage.conversationId)
put("message_id", messageId) put("message_id", bridgeLoggedMessage.messageId)
put("message_text", messageTextContent) put("message_text", messageTextContent)
}) })
} }
@ -213,7 +231,6 @@ class LoggerWrapper(
} }
} }
} }
}
fun purgeAll(maxAge: Long? = null) { fun purgeAll(maxAge: Long? = null) {
coroutineScope.launch { coroutineScope.launch {
@ -257,8 +274,7 @@ class LoggerWrapper(
}) { }) {
return false return false
} }
runBlocking { runBlocking(coroutineScope.coroutineContext) {
withContext(coroutineScope.coroutineContext) {
database.insert("stories", null, ContentValues().apply { database.insert("stories", null, ContentValues().apply {
put("user_id", userId) put("user_id", userId)
put("added_timestamp", System.currentTimeMillis()) put("added_timestamp", System.currentTimeMillis())
@ -269,7 +285,6 @@ class LoggerWrapper(
put("encryption_iv", iv) put("encryption_iv", iv)
}) })
} }
}
return true return true
} }
@ -282,8 +297,7 @@ class LoggerWrapper(
eventType: String, eventType: String,
data: String data: String
) { ) {
runBlocking { runBlocking(coroutineScope.coroutineContext) {
withContext(coroutineScope.coroutineContext) {
database.insert("tracker_events", null, ContentValues().apply { database.insert("tracker_events", null, ContentValues().apply {
put("timestamp", System.currentTimeMillis()) put("timestamp", System.currentTimeMillis())
put("conversation_id", conversationId) put("conversation_id", conversationId)
@ -296,7 +310,6 @@ class LoggerWrapper(
}) })
} }
} }
}
fun deleteTrackerLog(id: Int) { fun deleteTrackerLog(id: Int) {
coroutineScope.launch { coroutineScope.launch {
@ -389,6 +402,26 @@ class LoggerWrapper(
} }
} }
fun getConversationInfo(conversationId: String): ConversationInfo? {
val participantSize = database.rawQuery("SELECT COUNT(DISTINCT user_id) FROM messages WHERE conversation_id = ?", arrayOf(conversationId)).use {
if (!it.moveToFirst()) return null
it.getInt(0)
}
val groupTitle = if (participantSize > 2) database.rawQuery("SELECT group_title FROM messages WHERE conversation_id = ? AND group_title IS NOT NULL LIMIT 1", arrayOf(conversationId)).use {
if (!it.moveToFirst()) return@use null
it.getStringOrNull("group_title")
} else null
val usernames = database.rawQuery("SELECT DISTINCT username FROM messages WHERE conversation_id = ?", arrayOf(conversationId)).use {
val usernames = mutableListOf<String>()
while (it.moveToNext()) {
usernames.add(it.getString(0))
}
usernames
}
return ConversationInfo(conversationId, participantSize, groupTitle, usernames)
}
fun getMessageEdits(conversationId: String, messageId: Long): List<LoggedMessageEdit> { fun getMessageEdits(conversationId: String, messageId: Long): List<LoggedMessageEdit> {
val edits = mutableListOf<LoggedMessageEdit>() val edits = mutableListOf<LoggedMessageEdit>()
database.rawQuery( database.rawQuery(
@ -414,13 +447,18 @@ class LoggerWrapper(
): List<LoggedMessage> { ): List<LoggedMessage> {
val messages = mutableListOf<LoggedMessage>() val messages = mutableListOf<LoggedMessage>()
database.rawQuery( database.rawQuery(
"SELECT * FROM messages WHERE conversation_id = ? AND added_timestamp ${if (reverseOrder) "<" else ">"} ? ORDER BY added_timestamp ${if (reverseOrder) "DESC" else "ASC"}", "SELECT * FROM messages WHERE conversation_id = ? AND send_timestamp ${if (reverseOrder) "<" else ">"} ? ORDER BY send_timestamp ${if (reverseOrder) "DESC" else "ASC"}",
arrayOf(conversationId, fromTimestamp.toString()) arrayOf(conversationId, fromTimestamp.toString())
).use { ).use {
while (it.moveToNext() && messages.size < limit) { while (it.moveToNext() && messages.size < limit) {
val message = LoggedMessage( val message = LoggedMessage(
messageId = it.getLongOrNull("message_id") ?: continue, messageId = it.getLongOrNull("message_id") ?: continue,
timestamp = it.getLongOrNull("added_timestamp") ?: continue, conversationId = it.getStringOrNull("conversation_id") ?: continue,
userId = it.getStringOrNull("user_id") ?: continue,
username = it.getStringOrNull("username") ?: continue,
sendTimestamp = it.getLongOrNull("send_timestamp") ?: continue,
addedTimestamp = it.getLongOrNull("added_timestamp") ?: continue,
groupTitle = it.getStringOrNull("group_title"),
messageData = it.getBlobOrNull("message_data") ?: continue messageData = it.getBlobOrNull("message_data") ?: continue
) )
if (filter != null && !filter(message)) continue if (filter != null && !filter(message)) continue

View File

@ -7,6 +7,7 @@ import android.graphics.drawable.shapes.Shape
import android.os.DeadObjectException import android.os.DeadObjectException
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonParser import com.google.gson.JsonParser
import me.rhunk.snapenhance.bridge.logger.BridgeLoggedMessage
import me.rhunk.snapenhance.common.data.ContentType import me.rhunk.snapenhance.common.data.ContentType
import me.rhunk.snapenhance.common.data.MessageState import me.rhunk.snapenhance.common.data.MessageState
import me.rhunk.snapenhance.common.data.QuotedMessageContentStatus import me.rhunk.snapenhance.common.data.QuotedMessageContentStatus
@ -40,6 +41,9 @@ class MessageLogger : Feature("MessageLogger",
private val threadPool = Executors.newFixedThreadPool(10) private val threadPool = Executors.newFixedThreadPool(10)
private val usernameCache = EvictingMap<String, String>(500) // user id -> username
private val groupTitleCache = EvictingMap<String, String?>(500) // conversation id -> group title
private val cachedIdLinks = EvictingMap<Long, Long>(500) // client id -> server id private val cachedIdLinks = EvictingMap<Long, Long>(500) // client id -> server id
private val fetchedMessages = mutableListOf<Long>() // list of unique message ids private val fetchedMessages = mutableListOf<Long>() // list of unique message ids
private val deletedMessageCache = EvictingMap<Long, JsonObject>(200) // unique message id -> message json object private val deletedMessageCache = EvictingMap<Long, JsonObject>(200) // unique message id -> message json object
@ -127,7 +131,21 @@ class MessageLogger : Feature("MessageLogger",
threadPool.execute { threadPool.execute {
try { try {
loggerInterface.addMessage(conversationId, uniqueMessageIdentifier, context.gson.toJson(messageInstance).toByteArray(Charsets.UTF_8)) loggerInterface.addMessage(
BridgeLoggedMessage().also {
it.messageId = uniqueMessageIdentifier
it.conversationId = conversationId
it.userId = event.message.senderId.toString()
it.username = usernameCache.getOrPut(it.userId) {
context.database.getFriendInfo(it.userId)?.mutableUsername ?: it.userId
}
it.sendTimestamp = event.message.messageMetadata?.createdAt ?: System.currentTimeMillis()
it.groupTitle = groupTitleCache.getOrPut(conversationId) {
context.database.getFeedEntryByConversationId(conversationId)?.feedDisplayName ?: conversationId
}
it.messageData = context.gson.toJson(messageInstance).toByteArray(Charsets.UTF_8)
}
)
} catch (ignored: DeadObjectException) {} } catch (ignored: DeadObjectException) {}
} }