From f6ec3e8babd466485d17d2ddd8ac672eecad6fe2 Mon Sep 17 00:00:00 2001
From: James Rich <2199651+jamesarich@users.noreply.github.com>
Date: Tue, 15 Oct 2024 04:07:22 -0500
Subject: [PATCH] feat: Add notification for new node seen (#1316)
* Add notification for new node seen
This change adds a new notification that is displayed when a new node is seen.
The notification includes the node's
name.
It also updates the message notification to include the node name.
* Show individual notification for each new node
* Add new nodes notifications
Adds a new notification channel for new nodes seen.
This channel is set to high importance with sound and light.
---
.../geeksville/mesh/service/MeshService.kt | 5 ++
.../mesh/service/MeshServiceNotifications.kt | 62 +++++++++++++++++++
app/src/main/res/values/strings.xml | 1 +
3 files changed, 68 insertions(+)
diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt
index 3f5111b27..1e1bcd018 100644
--- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt
+++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt
@@ -803,6 +803,8 @@ class MeshService : Service(), Logging {
// Update our DB of users based on someone sending out a User subpacket
private fun handleReceivedUser(fromNum: Int, p: MeshProtos.User, channel: Int = 0) {
updateNodeInfo(fromNum) {
+ val newNode = (it.isUnknownUser && p.hwModel != MeshProtos.HardwareModel.UNSET)
+
val keyMatch = !it.hasPKC || it.user.publicKey == p.publicKey
it.user = if (keyMatch) p else p.copy {
warn("Public key mismatch from $longName ($shortName)")
@@ -811,6 +813,9 @@ class MeshService : Service(), Logging {
it.longName = p.longName
it.shortName = p.shortName
it.channel = channel
+ if (newNode) {
+ serviceNotifications.showNewNodeSeenNotification(it)
+ }
}
}
diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt b/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt
index af4e18280..2b5b01a92 100644
--- a/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt
+++ b/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt
@@ -19,12 +19,14 @@ import com.geeksville.mesh.MainActivity
import com.geeksville.mesh.R
import com.geeksville.mesh.TelemetryProtos.LocalStats
import com.geeksville.mesh.android.notificationManager
+import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.util.PendingIntentCompat
import java.io.Closeable
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
+@Suppress("TooManyFunctions")
class MeshServiceNotifications(
private val context: Context
) : Closeable {
@@ -81,6 +83,30 @@ class MeshServiceNotifications(
return channelId
}
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun createNewNodeNotificationChannel(): String {
+ val channelId = "new_nodes"
+ val channelName = context.getString(R.string.meshtastic_new_nodes_notifications)
+ val channel = NotificationChannel(
+ channelId,
+ channelName,
+ NotificationManager.IMPORTANCE_HIGH
+ ).apply {
+ lightColor = Color.BLUE
+ lockscreenVisibility = Notification.VISIBILITY_PUBLIC
+ setShowBadge(true)
+ setSound(
+ RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
+ AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .build()
+ )
+ }
+ notificationManager.createNotificationChannel(channel)
+ return channelId
+ }
+
private val channelId: String by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
@@ -101,6 +127,14 @@ class MeshServiceNotifications(
}
}
+ private val newNodeChannelId: String by lazy {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ createNewNodeNotificationChannel()
+ } else {
+ ""
+ }
+ }
+
private fun formatStatsString(stats: LocalStats?, currentStatsUpdatedAtMillis: Long?): String {
val updatedAt = "Next update at: ${
currentStatsUpdatedAtMillis?.let {
@@ -139,6 +173,13 @@ class MeshServiceNotifications(
createMessageNotification(name, message)
)
+ fun showNewNodeSeenNotification(node: NodeEntity) {
+ notificationManager.notify(
+ node.num, // show unique notifications
+ createNewNodeSeenNotification(node.user.shortName, node.user.longName)
+ )
+ }
+
private val openAppIntent: PendingIntent by lazy {
PendingIntent.getActivity(
context,
@@ -224,6 +265,27 @@ class MeshServiceNotifications(
return messageNotificationBuilder.build()
}
+ lateinit var newNodeSeenNotificationBuilder: NotificationCompat.Builder
+ private fun createNewNodeSeenNotification(name: String, message: String? = null): Notification {
+ if (!::newNodeSeenNotificationBuilder.isInitialized) {
+ newNodeSeenNotificationBuilder = commonBuilder(newNodeChannelId)
+ }
+ with(newNodeSeenNotificationBuilder) {
+ priority = NotificationCompat.PRIORITY_DEFAULT
+ setCategory(Notification.CATEGORY_STATUS)
+ setAutoCancel(true)
+ setContentTitle("New Node Seen: $name")
+ message?.let {
+ setContentText(it)
+ setStyle(
+ NotificationCompat.BigTextStyle()
+ .bigText(message),
+ )
+ }
+ }
+ return newNodeSeenNotificationBuilder.build()
+ }
+
override fun close() {
largeIcon?.recycle()
largeIcon = null
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e426926fa..3576597ac 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -271,4 +271,5 @@
Public key mismatch
The public key does not match the recorded key. You may remove the node and let it exchange keys again, but this may indicate a more serious security problem. Contact the user through another trusted channel, to determine if the key change was due to a factory reset or other intentional action.
Request user info
+ New nodes notifications