Skip to content

Commit

Permalink
[health] expose connection, battery and charging statuses
Browse files Browse the repository at this point in the history
  • Loading branch information
capcom6 committed Jan 22, 2025
1 parent 665ad24 commit e582aaf
Show file tree
Hide file tree
Showing 11 changed files with 249 additions and 13 deletions.
16 changes: 16 additions & 0 deletions app/src/main/java/me/capcom/smsgateway/domain/HealthResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package me.capcom.smsgateway.domain

import me.capcom.smsgateway.BuildConfig
import me.capcom.smsgateway.modules.health.domain.CheckResult
import me.capcom.smsgateway.modules.health.domain.HealthResult
import me.capcom.smsgateway.modules.health.domain.Status

class HealthResponse(
healthResult: HealthResult,

val version: String = BuildConfig.VERSION_NAME,
val releaseId: Int = BuildConfig.VERSION_CODE,
) {
val status: Status = healthResult.status
val checks: Map<String, CheckResult> = healthResult.checks
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package me.capcom.smsgateway.modules.connection

enum class CellularNetworkType {
None,
Unknown,
Mobile2G,
Mobile3G,
Mobile4G,
Mobile5G,
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
package me.capcom.smsgateway.modules.connection

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.os.Build
import android.telephony.TelephonyManager
import androidx.core.app.ActivityCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import me.capcom.smsgateway.modules.health.domain.CheckResult
import me.capcom.smsgateway.modules.health.domain.Status
import me.capcom.smsgateway.modules.logs.LogsService
import me.capcom.smsgateway.modules.logs.db.LogEntry
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

class ConnectionService(context: Context) : KoinComponent {
class ConnectionService(
private val context: Context
) : KoinComponent {
private val _status = MutableLiveData(false)
val status: LiveData<Boolean> = _status

Expand Down Expand Up @@ -53,6 +61,117 @@ class ConnectionService(context: Context) : KoinComponent {
}
}

fun healthCheck(): Map<String, CheckResult> {
val status = when (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
true -> when (_status.value) {
true -> Status.PASS
else -> Status.FAIL
}

false -> Status.PASS
}
val transport = transportType
val cellularType = cellularNetworkType

return mapOf(
"status" to CheckResult(
status,
when (status) {
Status.PASS -> 1L
else -> 0L
},
"boolean",
"Internet connection status"
),
"transport" to CheckResult(
when (transport.isEmpty()) {
true -> Status.FAIL
false -> Status.PASS
},
transport.sumOf { it.value }.toLong(),
"flags",
"Network transport type"
),
"cellular" to CheckResult(
Status.PASS,
cellularType.ordinal.toLong(),
"index",
"Cellular network type"
)
)
}

val transportType: Set<TransportType>
get() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return setOf(TransportType.Unknown)

val result = mutableSetOf<TransportType>()

val nw = connectivityManager.activeNetwork ?: return result
val actNw = connectivityManager.getNetworkCapabilities(nw) ?: return result

if (actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
result.add(TransportType.WiFi)
}
if (actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
result.add(TransportType.Ethernet)
}
if (actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
result.add(TransportType.Cellular)
}

return result;
}

val cellularNetworkType: CellularNetworkType
get() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return CellularNetworkType.Unknown

val transport = transportType

if (transport.contains(TransportType.Unknown)) {
return CellularNetworkType.Unknown
}
if (!transport.contains(TransportType.Cellular)) {
return CellularNetworkType.None
}

val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.READ_PHONE_STATE
) != PackageManager.PERMISSION_GRANTED
) {
return CellularNetworkType.Unknown
}
when (tm.dataNetworkType) {
TelephonyManager.NETWORK_TYPE_GPRS,
TelephonyManager.NETWORK_TYPE_EDGE,
TelephonyManager.NETWORK_TYPE_CDMA,
TelephonyManager.NETWORK_TYPE_1xRTT,
TelephonyManager.NETWORK_TYPE_IDEN,
TelephonyManager.NETWORK_TYPE_GSM -> return CellularNetworkType.Mobile2G

TelephonyManager.NETWORK_TYPE_UMTS,
TelephonyManager.NETWORK_TYPE_EVDO_0,
TelephonyManager.NETWORK_TYPE_EVDO_A,
TelephonyManager.NETWORK_TYPE_HSDPA,
TelephonyManager.NETWORK_TYPE_HSUPA,
TelephonyManager.NETWORK_TYPE_HSPA,
TelephonyManager.NETWORK_TYPE_EVDO_B,
TelephonyManager.NETWORK_TYPE_EHRPD,
TelephonyManager.NETWORK_TYPE_HSPAP,
TelephonyManager.NETWORK_TYPE_TD_SCDMA -> return CellularNetworkType.Mobile3G

TelephonyManager.NETWORK_TYPE_LTE,
TelephonyManager.NETWORK_TYPE_IWLAN, 19 -> return CellularNetworkType.Mobile4G

TelephonyManager.NETWORK_TYPE_NR -> return CellularNetworkType.Mobile5G
}

return CellularNetworkType.Unknown
}

init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
connectivityManager.registerDefaultNetworkCallback(networkCallback)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package me.capcom.smsgateway.modules.connection

enum class TransportType(
val value: Int
) {
Unknown(1),
Cellular(2),
WiFi(4),
Ethernet(8),
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
package me.capcom.smsgateway.modules.health

import me.capcom.smsgateway.modules.connection.ConnectionService
import me.capcom.smsgateway.modules.health.domain.HealthResult
import me.capcom.smsgateway.modules.health.domain.Status
import me.capcom.smsgateway.modules.health.monitors.BatteryMonitor
import me.capcom.smsgateway.modules.messages.MessagesService

class HealthService(
private val messagesSvc: MessagesService,
private val connectionSvc: ConnectionService,
private val batteryMon: BatteryMonitor,
) {

fun healthCheck(): HealthResult {
val messagesChecks = messagesSvc.healthCheck()
val allChecks = messagesChecks.mapKeys { "messages:${it.key}" }
val connectionChecks = connectionSvc.healthCheck()
val batteryChecks = batteryMon.healthCheck()

val allChecks = messagesChecks.mapKeys { "messages:${it.key}" } +
connectionChecks.mapKeys { "connection:${it.key}" } +
batteryChecks.mapKeys { "battery:${it.key}" }

return HealthResult(
when {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import me.capcom.smsgateway.modules.health.HealthService
import me.capcom.smsgateway.modules.health.monitors.BatteryMonitor
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module

val healthModule = module {
single { HealthService(get()) }
singleOf(::BatteryMonitor)
singleOf(::HealthService)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package me.capcom.smsgateway.modules.health.monitors

import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import me.capcom.smsgateway.modules.health.domain.CheckResult
import me.capcom.smsgateway.modules.health.domain.Status

class BatteryMonitor(
private val context: Context
) {
fun healthCheck(): Map<String, CheckResult> {
val batteryStatus: Intent? = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
.let { ifilter ->
context.registerReceiver(null, ifilter)
}

val status: Int = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
val isCharging: Boolean = status == BatteryManager.BATTERY_STATUS_CHARGING

// How are we charging?
val chargePlug: Int = batteryStatus?.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) ?: -1
val usbCharge: Boolean = chargePlug == BatteryManager.BATTERY_PLUGGED_USB
val acCharge: Boolean = chargePlug == BatteryManager.BATTERY_PLUGGED_AC

val batteryPct: Float? = batteryStatus?.let { intent ->
val level: Int = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val scale: Int = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
level * 100 / scale.toFloat()
}

val levelStatus = batteryPct?.let {
when {
it < 10 -> Status.FAIL
it < 25 -> Status.WARN
else -> Status.PASS
}
} ?: Status.PASS

return mapOf(
"level" to CheckResult(
levelStatus,
batteryPct?.toLong() ?: 0L,
"percent",
"Battery level in percent"
),
"charging" to CheckResult(
Status.PASS,
when {
acCharge -> 2L
usbCharge -> 4L
else -> 0L
} + when (isCharging) {
true -> 1L
false -> 0L
},
"flags",
"Is the phone charging?"
),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ import io.ktor.server.routing.get
import io.ktor.server.routing.route
import io.ktor.server.routing.routing
import io.ktor.util.date.GMTDate
import me.capcom.smsgateway.BuildConfig
import me.capcom.smsgateway.R
import me.capcom.smsgateway.domain.HealthResponse
import me.capcom.smsgateway.extensions.configure
import me.capcom.smsgateway.modules.health.HealthService
import me.capcom.smsgateway.modules.health.domain.Status
Expand Down Expand Up @@ -119,12 +119,7 @@ class WebService : Service() {
Status.WARN -> HttpStatusCode.OK
Status.PASS -> HttpStatusCode.OK
},
mapOf(
"status" to healthResult.status,
"version" to BuildConfig.VERSION_NAME,
"releaseId" to BuildConfig.VERSION_CODE,
"checks" to healthResult.checks
)
HealthResponse(healthResult)
)
}
authenticate("auth-basic") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package me.capcom.smsgateway.modules.ping.events

import me.capcom.smsgateway.domain.HealthResponse
import me.capcom.smsgateway.modules.events.AppEvent

class PingEvent : AppEvent(TYPE) {
class PingEvent(
val health: HealthResponse,
) : AppEvent(TYPE) {
companion object {
const val TYPE = "PingEvent"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import me.capcom.smsgateway.R
import me.capcom.smsgateway.domain.HealthResponse
import me.capcom.smsgateway.modules.events.EventBus
import me.capcom.smsgateway.modules.health.HealthService
import me.capcom.smsgateway.modules.notifications.NotificationsService
import me.capcom.smsgateway.modules.ping.PingSettings
import me.capcom.smsgateway.modules.ping.events.PingEvent
Expand All @@ -30,6 +32,7 @@ class PingForegroundService : Service() {
private val eventBus = get<EventBus>()

private val notificationsSvc: NotificationsService by inject()
private val healthService: HealthService by inject()

private val wakeLock: PowerManager.WakeLock by lazy {
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
Expand All @@ -51,7 +54,9 @@ class PingForegroundService : Service() {
while (!stopRequested) {
try {
Log.d(this.javaClass.name, "Sending ping")
scope.launch { eventBus.emit(PingEvent()) }
scope.launch {
eventBus.emit(PingEvent(HealthResponse(healthService.healthCheck())))
}

Thread.sleep(interval * 1000L)
} catch (_: Throwable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ class EventsReceiver : EventsReceiver() {
eventBus.collect<PingEvent> {
Log.d("EventsReceiver", "Event: $it")

get<WebHooksService>().emit(WebHookEvent.SystemPing, object {})
get<WebHooksService>().emit(
WebHookEvent.SystemPing,
mapOf("health" to it.health)
)
}
}

Expand Down

0 comments on commit e582aaf

Please sign in to comment.