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)