Add local device stats to the service notification (#1307)

This commit adds the local device stats to the service notification. This information includes the number of online and total nodes, as well as other local stats. It also updates the notification summary and adds local stats telemetry handling.
pull/1312/head
James Rich 2024-10-13 06:10:28 -05:00 zatwierdzone przez GitHub
rodzic b503c10789
commit 38942ec557
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
2 zmienionych plików z 105 dodań i 19 usunięć

Wyświetl plik

@ -9,16 +9,17 @@ import android.os.IBinder
import android.os.RemoteException
import androidx.core.app.ServiceCompat
import androidx.core.location.LocationCompat
import com.geeksville.mesh.analytics.DataPair
import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.*
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
import com.geeksville.mesh.LocalOnlyProtos.LocalModuleConfig
import com.geeksville.mesh.MeshProtos.MeshPacket
import com.geeksville.mesh.MeshProtos.ToRadio
import com.geeksville.mesh.TelemetryProtos.LocalStats
import com.geeksville.mesh.analytics.DataPair
import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.android.hasLocationPermission
import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.database.MeshLogRepository
import com.geeksville.mesh.database.PacketRepository
import com.geeksville.mesh.database.entity.MeshLog
@ -138,6 +139,7 @@ class MeshService : Service(), Logging {
}
private var previousSummary: String? = null
private var previousStats: LocalStats? = null
/// A mapping of receiver class name to package name - used for explicit broadcasts
private val clientPackages = mutableMapOf<String, String>()
@ -167,6 +169,10 @@ class MeshService : Service(), Logging {
ConnectionState.DEVICE_SLEEP -> getString(R.string.device_sleeping)
}
private var localStatsTelemetry: TelemetryProtos.Telemetry? = null
private val localStats: LocalStats? get() = localStatsTelemetry?.localStats
private val localStatsUpdatedAtMillis: Long? get() = localStatsTelemetry?.time?.let { it * 1000L }
/**
* start our location requests (if they weren't already running)
*/
@ -823,11 +829,21 @@ class MeshService : Service(), Logging {
}
}
private fun handleLocalStats(stats: TelemetryProtos.Telemetry) {
localStatsTelemetry = stats
maybeUpdateServiceStatusNotification()
}
/// Update our DB of users based on someone sending out a Telemetry subpacket
private fun handleReceivedTelemetry(
fromNum: Int,
t: TelemetryProtos.Telemetry,
) {
if (t.hasLocalStats()) {
handleLocalStats(t)
}
updateNodeInfo(fromNum) {
when {
t.hasDeviceMetrics() -> it.deviceTelemetry = t
@ -1272,10 +1288,30 @@ class MeshService : Service(), Logging {
}
private fun maybeUpdateServiceStatusNotification() {
var update = false
val currentSummary = notificationSummary
if (previousSummary == null || !previousSummary.equals(currentSummary)) {
serviceNotifications.updateServiceStateNotification(currentSummary)
val currentStats = localStats
val currentStatsUpdatedAtMillis = localStatsUpdatedAtMillis
if (
!currentSummary.isNullOrBlank() &&
(previousSummary == null || !previousSummary.equals(currentSummary))
) {
previousSummary = currentSummary
update = true
}
if (
currentStats != null &&
(previousStats == null || !(previousStats?.equals(currentStats) ?: false))
) {
previousStats = currentStats
update = true
}
if (update) {
serviceNotifications.updateServiceStateNotification(
summaryString = currentSummary,
localStats = currentStats,
currentStatsUpdatedAtMillis = currentStatsUpdatedAtMillis
)
}
}

Wyświetl plik

@ -17,14 +17,22 @@ import androidx.core.app.NotificationCompat
import androidx.core.graphics.drawable.toBitmapOrNull
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.util.PendingIntentCompat
import java.io.Closeable
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class MeshServiceNotifications(
private val context: Context
) : Closeable {
companion object {
private const val FIFTEEN_MINUTES_IN_MILLIS = 15L * 60 * 1000
}
private val notificationManager: NotificationManager get() = context.notificationManager
// We have two notification channels: one for general service status and another one for messages
@ -93,11 +101,37 @@ class MeshServiceNotifications(
}
}
fun updateServiceStateNotification(summaryString: String) =
private fun formatStatsString(stats: LocalStats?, currentStatsUpdatedAtMillis: Long?): String {
val updatedAt = "Next update at: ${
currentStatsUpdatedAtMillis?.let {
val date = Date(it + FIFTEEN_MINUTES_IN_MILLIS) // Add 15 minutes in milliseconds
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
dateFormat.format(date)
} ?: "???"
}"
val statsJoined = stats?.allFields?.mapNotNull { (k, v) ->
if (k.name == "num_online_nodes" || k.name == "num_total_nodes") {
return@mapNotNull null
}
"${
k.name.replace('_', ' ').split(" ")
.joinToString(" ") { it.replaceFirstChar { char -> char.uppercase() } }
}=$v"
}?.joinToString("\n") ?: "No Local Stats"
return "$updatedAt\n$statsJoined"
}
fun updateServiceStateNotification(
summaryString: String? = null,
localStats: LocalStats? = null,
currentStatsUpdatedAtMillis: Long? = null,
) {
val statsString = formatStatsString(localStats, currentStatsUpdatedAtMillis)
notificationManager.notify(
notifyId,
createServiceStateNotification(summaryString)
createServiceStateNotification(summaryString.orEmpty(), statsString)
)
}
fun updateMessageNotification(name: String, message: String) =
notificationManager.notify(
@ -139,28 +173,44 @@ class MeshServiceNotifications(
builder.setSmallIcon(
// vector form icons don't work reliably on older androids
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) R.drawable.app_icon_novect
else R.drawable.app_icon
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
R.drawable.app_icon_novect
} else {
R.drawable.app_icon
}
)
.setLargeIcon(largeIcon)
}
return builder
}
fun createServiceStateNotification(summaryString: String): Notification {
val builder = commonBuilder(channelId)
with(builder) {
lateinit var serviceNotificationBuilder: NotificationCompat.Builder
fun createServiceStateNotification(name: String, message: String? = null): Notification {
if (!::serviceNotificationBuilder.isInitialized) {
serviceNotificationBuilder = commonBuilder(channelId)
}
with(serviceNotificationBuilder) {
priority = NotificationCompat.PRIORITY_MIN
setCategory(Notification.CATEGORY_SERVICE)
setOngoing(true)
setContentTitle(summaryString) // leave this off for now so our notification looks smaller
setContentTitle(name)
message?.let {
setContentText(it)
setStyle(
NotificationCompat.BigTextStyle()
.bigText(message),
)
}
}
return builder.build()
return serviceNotificationBuilder.build()
}
lateinit var messageNotificationBuilder: NotificationCompat.Builder
private fun createMessageNotification(name: String, message: String): Notification {
val builder = commonBuilder(messageChannelId)
with(builder) {
if (!::messageNotificationBuilder.isInitialized) {
messageNotificationBuilder = commonBuilder(messageChannelId)
}
with(messageNotificationBuilder) {
priority = NotificationCompat.PRIORITY_DEFAULT
setCategory(Notification.CATEGORY_MESSAGE)
setAutoCancel(true)
@ -171,7 +221,7 @@ class MeshServiceNotifications(
.bigText(message),
)
}
return builder.build()
return messageNotificationBuilder.build()
}
override fun close() {