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 ca55300c3..c6fb5c45a 100644
--- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt
+++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt
@@ -213,6 +213,9 @@ class MeshService : Service(), Logging {
private var locationFlow: Job? = null
private var mqttMessageFlow: Job? = null
+ private val batteryPercentUnsupported = 0.0
+ private val batteryPercentLowThreshold = 20.0
+
private fun getSenderName(packet: DataPacket?): String {
val name = nodeDBbyID[packet?.from]?.user?.longName
return name ?: getString(R.string.unknown_username)
@@ -939,7 +942,16 @@ class MeshService : Service(), Logging {
}
updateNodeInfo(fromNum) {
when {
- t.hasDeviceMetrics() -> it.deviceTelemetry = t
+ t.hasDeviceMetrics() -> {
+ it.deviceTelemetry = t
+ val isRemote = (fromNum != myNodeNum)
+ if (fromNum == myNodeNum || (isRemote && it.isFavorite)) {
+ if (t.deviceMetrics.voltage > batteryPercentUnsupported &&
+ t.deviceMetrics.batteryLevel < batteryPercentLowThreshold) {
+ serviceNotifications.showOrUpdateLowBatteryNotification(it, isRemote)
+ }
+ }
+ }
t.hasEnvironmentMetrics() -> it.environmentTelemetry = t
t.hasPowerMetrics() -> it.powerTelemetry = t
}
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 8ffaee120..fd2d2bd85 100644
--- a/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt
+++ b/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt
@@ -65,6 +65,8 @@ class MeshServiceNotifications(
createMessageNotificationChannel()
createAlertNotificationChannel()
createNewNodeNotificationChannel()
+ createLowBatteryNotificationChannel()
+ createLowBatteryRemoteNotificationChannel()
}
}
@@ -173,6 +175,60 @@ class MeshServiceNotifications(
return channelId
}
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun createLowBatteryNotificationChannel(): String {
+ val channelId = "low_battery"
+ if (notificationManager.getNotificationChannel(channelId) == null) {
+ val channelName = context.getString(R.string.meshtastic_low_battery_notifications)
+ val channel = NotificationChannel(
+ channelId,
+ channelName,
+ NotificationManager.IMPORTANCE_HIGH
+ ).apply {
+ lightColor = notificationLightColor
+ 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
+ }
+
+ // FIXME, Once we get a dedicated settings page in the app, this function should be removed and
+ // the feature should be implemented in the regular low battery notification stuff
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun createLowBatteryRemoteNotificationChannel(): String {
+ val channelId = "low_battery_remote"
+ if (notificationManager.getNotificationChannel(channelId) == null) {
+ val channelName = context.getString(R.string.meshtastic_low_battery_temporary_remote_notifications)
+ val channel = NotificationChannel(
+ channelId,
+ channelName,
+ NotificationManager.IMPORTANCE_HIGH
+ ).apply {
+ lightColor = notificationLightColor
+ 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()
@@ -209,6 +265,24 @@ class MeshServiceNotifications(
}
}
+ private val lowBatteryChannelId: String by lazy {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ createLowBatteryNotificationChannel()
+ } else {
+ ""
+ }
+ }
+
+ // FIXME, Once we get a dedicated settings page in the app, this function should be removed and
+ // the feature should be implemented in the regular low battery notification stuff
+ private val lowBatteryRemoteChannelId: String by lazy {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ createLowBatteryRemoteNotificationChannel()
+ } else {
+ ""
+ }
+ }
+
private fun LocalStats?.formatToString(): String = this?.allFields?.mapNotNull { (k, v) ->
when (k.name) {
"num_online_nodes", "num_total_nodes" -> return@mapNotNull null
@@ -258,6 +332,13 @@ class MeshServiceNotifications(
)
}
+ fun showOrUpdateLowBatteryNotification(node: NodeEntity, isRemote: Boolean) {
+ notificationManager.notify(
+ node.num, // show unique notifications
+ createLowBatteryNotification(node, isRemote)
+ )
+ }
+
private val openAppIntent: PendingIntent by lazy {
PendingIntent.getActivity(
context,
@@ -409,4 +490,51 @@ class MeshServiceNotifications(
}
return newNodeSeenNotificationBuilder.build()
}
+
+ lateinit var lowBatteryRemoteNotificationBuilder: NotificationCompat.Builder
+ lateinit var lowBatteryNotificationBuilder: NotificationCompat.Builder
+ private fun createLowBatteryNotification(node: NodeEntity, isRemote: Boolean): Notification {
+ val tempNotificationBuilder: NotificationCompat.Builder = if (isRemote) {
+ if (!::lowBatteryRemoteNotificationBuilder.isInitialized) {
+ lowBatteryRemoteNotificationBuilder = commonBuilder(lowBatteryChannelId)
+ }
+ lowBatteryRemoteNotificationBuilder
+ } else {
+ if (!::lowBatteryNotificationBuilder.isInitialized) {
+ lowBatteryNotificationBuilder = commonBuilder(lowBatteryRemoteChannelId)
+ }
+ lowBatteryNotificationBuilder
+ }
+ with(tempNotificationBuilder) {
+ priority = NotificationCompat.PRIORITY_DEFAULT
+ setCategory(Notification.CATEGORY_STATUS)
+ setAutoCancel(true)
+ setShowWhen(true)
+ setOnlyAlertOnce(true)
+ setWhen(System.currentTimeMillis())
+ setContentTitle(
+ context.getString(R.string.low_battery_title).format(
+ node.shortName
+ )
+ )
+ val message = context.getString(R.string.low_battery_message).format(
+ node.longName,
+ node.deviceMetrics.batteryLevel
+ )
+ message.let {
+ setContentText(it)
+ setStyle(
+ NotificationCompat.BigTextStyle()
+ .bigText(it),
+ )
+ }
+ }
+ if (isRemote) {
+ lowBatteryRemoteNotificationBuilder = tempNotificationBuilder
+ return lowBatteryRemoteNotificationBuilder.build()
+ } else {
+ lowBatteryNotificationBuilder = tempNotificationBuilder
+ return lowBatteryNotificationBuilder.build()
+ }
+ }
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 51ac8501c..8eb0e9e25 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -337,4 +337,8 @@
Are you sure?
Device Role Documentation and the blog post about Choosing The Right Device Role.]]>
I know what I\'m doing.
+ Node %s has a low battery (%d%%)
+ Low battery notifications
+ Low battery: %s
+ Low battery notifications (favorite nodes)