Adds Robolectric-based androidHostTest coverage for the three notification
BroadcastReceivers. Verifies:
- ReplyReceiver sends a DataPacket derived from the contactKey then calls
appendOutgoingMessage followed by markConversationRead in that order.
- MarkAsReadReceiver invokes markConversationRead, ignores wrong actions,
and drops intents missing the contact key.
- ReactionReceiver dispatches a ServiceAction.Reaction and, on success,
calls markConversationRead. Failures in dispatch short-circuit markRead.
Uses the existing FakeRadioController and FakeMeshServiceNotifications
(marked open so tests can record calls) plus mokkery for ServiceRepository,
mirroring the pattern in SendMessageWorkerTest. Fakes are wired through a
per-test Koin graph to match each receiver's KoinComponent injection.
Also fixes a pre-existing compile break in MeshServiceNotificationsImplTest
that was missing the shortcutManager constructor argument.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Before cancelling a conversation notification in response to an inline
reply, post one final update that appends the outgoing text to the
MessagingStyle history, attributed to the local user. This gives
assistants such as Android Auto a tick to observe the sent message in
the notification's message history and surface a 'reply sent' style
confirmation before markConversationRead cancels the notification.
Extract the 'me' Person construction into buildMePerson() and share it
between showGroupSummary and createConversationNotification. The
conversation builder now optionally takes an extraOutgoingMessage which
is appended to the MessagingStyle (actions and when-timestamp continue
to be anchored on the last incoming message).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extract the 'clear unread count + cancel message notification' pair into
a single suspend helper on MeshServiceNotifications so ReplyReceiver,
MarkAsReadReceiver, and ReactionReceiver use one consistent code path.
ReactionReceiver now also clears unread and cancels the notification
once the reaction dispatch succeeds, matching the other receivers.
Receivers that only depended on PacketRepository for this pair drop
that injection.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reading notificationManager.activeNotifications immediately after
cancel() races with NotificationManagerService, so the count of
remaining children was unreliable and the summary could linger.
Drop it unconditionally — the next inbound message rebuilds it via
showGroupSummary().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After clearing a message notification we left the GROUP_KEY_MESSAGES
summary in place, which on Android Auto leaves a lingering HUN /
summary entry for the already-dismissed conversation. Cancel the
summary when no child notifications remain, and refresh it otherwise.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The MIN_CONTEXT_MESSAGES fallback (pull 3 most recent history
messages when unread < 3) was injecting already-read historical
messages into the MessagingStyle. On Android Auto, Gearhead reads
every message in the style aloud and shows them stacked on the
HUN, so old context was announced alongside the new one.
Just show the unread messages (up to MAX_HISTORY_MESSAGES). If
nothing is unread, don't post at all — we only call this from
paths that already check for fresh inbound content.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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>
Temporary diagnostic logging added while investigating why Android Auto
inline replies don't appear to dismiss the conversation notification
for some users. Remove or downgrade to verbose once confirmed working.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ReplyReceiver was only cancelling the notification after an inline
reply; PacketRepository.clearUnreadCount was never called, so the
message stayed 'unread' in the app (and in the Android Auto favorites
unread badges) even after the user replied from the HUN.
Mirror MarkAsReadReceiver by invoking clearUnreadCount with nowMillis
before cancelling the notification.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Gearhead's MsgNotifParser was rejecting Meshtastic notifications with:
- 'No semantic reply action found'
- 'No semantic mark-as-read action found'
- 'added an invalid shortcut'
Fixes:
- Tag reply action with SEMANTIC_ACTION_REPLY + setShowsUserInterface(false)
+ setAllowGeneratedReplies(true) so Gearhead/Assistant can surface it.
- Tag mark-as-read action with SEMANTIC_ACTION_MARK_AS_READ.
- Publish an on-demand long-lived conversation shortcut whose id matches
the notification's setShortcutId(contactKey). Previously only favorites
+ channels at index 0 had shortcuts, so DMs received on a non-zero
channel referenced an unpublished shortcut and Android Auto refused to
project them.
Verified on Pixel 6a + DHU 2.0: notifications now carry matching
long-lived shortcuts and project as messaging HUNs with reply, mark-read
and reaction actions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Notifications and message routing key channel conversations by the raw
protocol channel index (e.g. "2^all"), but publishShortcuts and the
car screen were re-indexing after filtering out unnamed channels, so
named channels after a gap would never match their notification's
shortcutId/locusId and their unread badge would stay at zero.
Preserve the original index via mapIndexedNotNull { index to settings }.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Move MeshtasticCarAppService, CarSession, CarScreen from app/auto
to the new :feature:auto module (meshtastic.android.library)
- Move automotive_app_desc.xml → auto_app_desc.xml (respects resourcePrefix)
- Move manifest entries (service + meta-data) into feature module so they
merge into app rather than living in the app manifest directly
- Fix HostValidator: use ApplicationInfo.FLAG_DEBUGGABLE instead of
BuildConfig.DEBUG (library modules don't ship their own BuildConfig)
- Fix stale unread counts: replace point-in-time getUnreadCount() with
flatMapLatest + per-conversation getUnreadCountFlow() so the car screen
invalidates on new messages, not just topology changes
- Fix ConversationShortcutManager: replace removeAllDynamicShortcuts +
addDynamicShortcuts with pushDynamicShortcut per conversation to
preserve usage/ranking history; remove stale shortcuts individually;
respect getMaxShortcutCountPerActivity() limit
- Fix SHORTCUT_CATEGORY_CONVERSATION: constant lives on ShortcutInfo,
not ShortcutManagerCompat
- Remove androidx.car.app dependency from :app (now owned by :feature:auto)
- Add :feature:auto to settings.gradle.kts and app dependencies
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>