mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
fix(auto): project messaging notifications to Android Auto
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>
This commit is contained in:
parent
36f770fd0b
commit
07772917c3
2 changed files with 68 additions and 1 deletions
|
|
@ -158,6 +158,34 @@ class ConversationShortcutManager(
|
|||
setPackage(context.packageName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a long-lived conversation shortcut exists for [contactKey]. Called on demand when a notification is about
|
||||
* to reference a shortcut id that may not have been pre-published (e.g., an incoming DM on a non-primary channel,
|
||||
* or from a non-favorite node). Android Auto requires a matching published shortcut to project the notification as
|
||||
* a messaging HUN.
|
||||
*/
|
||||
fun ensureConversationShortcut(contactKey: String, person: Person, label: String) {
|
||||
val alreadyPublished = ShortcutManagerCompat.getDynamicShortcuts(context).any { it.id == contactKey }
|
||||
if (alreadyPublished) return
|
||||
val shortcut =
|
||||
ShortcutInfoCompat.Builder(context, contactKey)
|
||||
.setShortLabel(label)
|
||||
.setLongLabel(label)
|
||||
.setLocusId(LocusIdCompat(contactKey))
|
||||
.setPerson(person)
|
||||
.setLongLived(true)
|
||||
.setCategories(setOf(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION))
|
||||
.setIntent(conversationIntent(contactKey))
|
||||
.build()
|
||||
try {
|
||||
ShortcutManagerCompat.pushDynamicShortcut(context, shortcut)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Logger.e(e) { "Failed to publish on-demand shortcut $contactKey" }
|
||||
} catch (e: IllegalStateException) {
|
||||
Logger.e(e) { "Failed to publish on-demand shortcut $contactKey" }
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPersonIcon(name: String, backgroundColor: Int, foregroundColor: Int): IconCompat {
|
||||
val size = ICON_SIZE
|
||||
val bitmap = createBitmap(size, size)
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ class MeshServiceNotificationsImpl(
|
|||
private val context: Context,
|
||||
private val packetRepository: Lazy<PacketRepository>,
|
||||
private val nodeRepository: Lazy<NodeRepository>,
|
||||
private val shortcutManager: Lazy<ConversationShortcutManager>,
|
||||
) : MeshServiceNotifications {
|
||||
|
||||
private val notificationManager = context.getSystemService<NotificationManager>()!!
|
||||
|
|
@ -618,6 +619,8 @@ class MeshServiceNotificationsImpl(
|
|||
}
|
||||
val lastMessage = history.last()
|
||||
|
||||
ensureShortcutForNotification(contactKey, isBroadcast, channelName, lastMessage)
|
||||
|
||||
builder
|
||||
.setCategory(Notification.CATEGORY_MESSAGE)
|
||||
.setAutoCancel(true)
|
||||
|
|
@ -773,6 +776,36 @@ class MeshServiceNotificationsImpl(
|
|||
}
|
||||
}
|
||||
|
||||
private fun ensureShortcutForNotification(
|
||||
contactKey: String,
|
||||
isBroadcast: Boolean,
|
||||
channelName: String?,
|
||||
lastMessage: Message,
|
||||
) {
|
||||
val person =
|
||||
if (isBroadcast) {
|
||||
Person.Builder().setName(channelName ?: contactKey).setKey(contactKey).build()
|
||||
} else {
|
||||
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,
|
||||
),
|
||||
)
|
||||
.build()
|
||||
}
|
||||
val label =
|
||||
when {
|
||||
isBroadcast -> channelName ?: contactKey
|
||||
else -> lastMessage.node.user.long_name.ifEmpty { lastMessage.node.user.short_name }
|
||||
}
|
||||
shortcutManager.value.ensureConversationShortcut(contactKey, person, label)
|
||||
}
|
||||
|
||||
private fun createReplyAction(contactKey: String): NotificationCompat.Action {
|
||||
val replyLabel = getString(Res.string.reply)
|
||||
val remoteInput = RemoteInput.Builder(KEY_TEXT_REPLY).setLabel(replyLabel).build()
|
||||
|
|
@ -792,6 +825,9 @@ class MeshServiceNotificationsImpl(
|
|||
|
||||
return NotificationCompat.Action.Builder(android.R.drawable.ic_menu_send, replyLabel, replyPendingIntent)
|
||||
.addRemoteInput(remoteInput)
|
||||
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
|
||||
.setShowsUserInterface(false)
|
||||
.setAllowGeneratedReplies(true)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
|
@ -810,7 +846,10 @@ class MeshServiceNotificationsImpl(
|
|||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
||||
)
|
||||
|
||||
return NotificationCompat.Action.Builder(android.R.drawable.ic_menu_view, label, pendingIntent).build()
|
||||
return NotificationCompat.Action.Builder(android.R.drawable.ic_menu_view, label, pendingIntent)
|
||||
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
|
||||
.setShowsUserInterface(false)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun createReactionAction(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue