From c1073f3e120758feffc823a3834227d1a876128f Mon Sep 17 00:00:00 2001 From: James Rich Date: Fri, 17 Apr 2026 09:12:48 -0500 Subject: [PATCH] fix(auto): don't re-post conversation notif on outgoing messages rememberDataPacket() was invoking handlePacketNotification() for outgoing packets too, which made our own reply race with the cancel issued by ReplyReceiver and repost the conversation with ourselves as the visible sender (lastMessage.node == ourNode). Also harden ensureShortcutForNotification for DMs: the remote contact is deterministic from contactKey (channel + nodeId), so derive the shortcut Person from the resolved contact node rather than whatever message happens to be newest in history. This keeps the Android Auto HUN labelled correctly even if the message list ends with an outgoing packet. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../core/data/manager/MeshDataHandlerImpl.kt | 2 +- .../service/MeshServiceNotificationsImpl.kt | 27 ++++++++++++------- .../meshtastic/core/service/ReplyReceiver.kt | 3 --- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerImpl.kt index 384f722d8..8b0ee1529 100644 --- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerImpl.kt +++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerImpl.kt @@ -357,7 +357,7 @@ class MeshDataHandlerImpl( read = fromLocal || isFiltered, filtered = isFiltered, ) - if (!isFiltered) { + if (!isFiltered && !fromLocal) { handlePacketNotification(dataPacket, contactKey, updateNotification) } } diff --git a/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshServiceNotificationsImpl.kt b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshServiceNotificationsImpl.kt index 7fba7c087..75ae47297 100644 --- a/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshServiceNotificationsImpl.kt +++ b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshServiceNotificationsImpl.kt @@ -782,26 +782,33 @@ class MeshServiceNotificationsImpl( channelName: String?, lastMessage: Message, ) { + val contactNode = + if (isBroadcast) { + null + } else { + // contactKey format: "${channel}${nodeId}"; the remote contact is the node keyed by nodeId, + // which is stable regardless of whether the latest message in history is incoming or outgoing. + val nodeId = contactKey.drop(1) + nodeRepository.value.getNode(nodeId) + } val person = if (isBroadcast) { Person.Builder().setName(channelName ?: contactKey).setKey(contactKey).build() } else { + val node = contactNode ?: lastMessage.node Person.Builder() - .setName(lastMessage.node.user.long_name) - .setKey(lastMessage.node.user.id) - .setIcon( - createPersonIcon( - lastMessage.node.user.short_name, - lastMessage.node.colors.second, - lastMessage.node.colors.first, - ), - ) + .setName(node.user.long_name) + .setKey(node.user.id) + .setIcon(createPersonIcon(node.user.short_name, node.colors.second, node.colors.first)) .build() } val label = when { isBroadcast -> channelName ?: contactKey - else -> lastMessage.node.user.long_name.ifEmpty { lastMessage.node.user.short_name } + else -> { + val node = contactNode ?: lastMessage.node + node.user.long_name.ifEmpty { node.user.short_name } + } } shortcutManager.value.ensureConversationShortcut(contactKey, person, label) } diff --git a/core/service/src/androidMain/kotlin/org/meshtastic/core/service/ReplyReceiver.kt b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/ReplyReceiver.kt index f265a10fd..13fd92758 100644 --- a/core/service/src/androidMain/kotlin/org/meshtastic/core/service/ReplyReceiver.kt +++ b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/ReplyReceiver.kt @@ -20,7 +20,6 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import androidx.core.app.RemoteInput -import co.touchlab.kermit.Logger import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch @@ -65,7 +64,6 @@ class ReplyReceiver : if (remoteInput != null) { val contactKey = intent.getStringExtra(CONTACT_KEY) ?: "" val message = remoteInput.getCharSequence(KEY_TEXT_REPLY)?.toString() ?: "" - Logger.d { "ReplyReceiver: onReceive contactKey='$contactKey' msgLen=${message.length}" } val pendingResult = goAsync() scope.launch { @@ -73,7 +71,6 @@ class ReplyReceiver : sendMessage(message, contactKey) packetRepository.clearUnreadCount(contactKey, nowMillis) meshServiceNotifications.cancelMessageNotification(contactKey) - Logger.d { "ReplyReceiver: reply flow complete for contactKey='$contactKey'" } } finally { pendingResult.finish() }