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