Fix notification spam, stale frame count, and neighbor flicker
- Post the ongoing status on a fresh LOW channel (cellguard_status_v2) and delete the legacy channel, whose importance was stuck at HIGH (a channel's importance is immutable after creation) and heads-up'd on every 4s poll. - Read the live capture total from the /proc/cgcap/frames header instead of the detector's snapshot, which could lag behind. - Accumulate neighbor towers across polls with a 120s TTL instead of publishing the raw per-poll getAllCellInfo list, which blinked empty whenever a scan returned only the serving cell.
This commit is contained in:
@@ -105,7 +105,8 @@ class DetectorService : Service() {
|
||||
val frameTowers = FrameDecoder.decodeFrames(framesText)
|
||||
val towers = if (fwTowers.isNotEmpty()) fwTowers else frameTowers
|
||||
val serving = towers.firstOrNull { it.registered } ?: towers.firstOrNull()
|
||||
val neighbors = towers.filter { it !== serving && !it.registered }.distinct()
|
||||
val currentNeighbors = towers.filter { it !== serving && !it.registered }.distinct()
|
||||
val neighbors = mergeNeighbors(currentNeighbors, serving)
|
||||
|
||||
val servingPlmn = status.plmn.takeIf { it.isNotBlank() && it != "?" }
|
||||
?: FrameDecoder.firstPlmn(framesText)
|
||||
@@ -128,7 +129,7 @@ class DetectorService : Service() {
|
||||
plmn = plmn,
|
||||
operator = operator,
|
||||
rat = rat,
|
||||
frames = status.frames,
|
||||
frames = parseTotal(framesText) ?: status.frames,
|
||||
blockEnabled = status.block,
|
||||
detectEnabled = status.detect,
|
||||
ratLock = status.ratLock,
|
||||
@@ -137,6 +138,35 @@ class DetectorService : Service() {
|
||||
)
|
||||
}
|
||||
|
||||
private data class SeenTower(val tower: Tower, val lastSeen: Long)
|
||||
private val neighborCache = LinkedHashMap<String, SeenTower>()
|
||||
|
||||
private fun towerKey(t: Tower): String =
|
||||
"${t.rat}|${t.mcc}${t.mnc}|${t.ci}|${t.pci}|${t.earfcn}"
|
||||
|
||||
/**
|
||||
* Neighbor towers arrive per-poll from getAllCellInfo(), which frequently
|
||||
* reports only the serving cell on any given scan. Publishing the raw per-poll
|
||||
* list makes the table blink empty every 4s. Instead accumulate seen neighbors
|
||||
* keyed by identity and age them out after [NEIGHBOR_TTL_MS], so a tower stays
|
||||
* listed (with its most recent signal) until it is genuinely gone.
|
||||
*/
|
||||
private fun mergeNeighbors(current: List<Tower>, serving: Tower?): List<Tower> {
|
||||
val now = System.currentTimeMillis()
|
||||
current.forEach { neighborCache[towerKey(it)] = SeenTower(it, now) }
|
||||
val servingKey = serving?.let { towerKey(it) }
|
||||
neighborCache.entries.removeAll { (k, v) ->
|
||||
k == servingKey || now - v.lastSeen > NEIGHBOR_TTL_MS
|
||||
}
|
||||
return neighborCache.values.sortedByDescending { it.lastSeen }.map { it.tower }
|
||||
}
|
||||
|
||||
/** True live capture count from the `# total=N` header line of /proc/cgcap/frames. */
|
||||
private fun parseTotal(framesText: String): Long? {
|
||||
val line = framesText.lineSequence().firstOrNull { it.startsWith("# total=") } ?: return null
|
||||
return Regex("total=(\\d+)").find(line)?.groupValues?.get(1)?.toLongOrNull()
|
||||
}
|
||||
|
||||
/** On-device heuristics merged with the shell detector's `suspect` token. */
|
||||
private fun buildSuspects(
|
||||
towers: List<Tower>,
|
||||
@@ -192,6 +222,11 @@ class DetectorService : Service() {
|
||||
|
||||
private fun createChannel() {
|
||||
val mgr = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
// A channel's importance is immutable after creation. Earlier builds created
|
||||
// "cellguard" at HIGH, so the ongoing status notification heads-up/sounds on
|
||||
// every 4s poll (the reported spam). The only fix is to abandon that id:
|
||||
// delete the legacy channel and post the silent status on a fresh LOW one.
|
||||
runCatching { mgr.deleteNotificationChannel(LEGACY_CHANNEL_ID) }
|
||||
// Ongoing status: LOW importance -> silent, no heads-up, no per-poll spam.
|
||||
if (mgr.getNotificationChannel(CHANNEL_ID) == null) {
|
||||
mgr.createNotificationChannel(
|
||||
@@ -315,11 +350,13 @@ class DetectorService : Service() {
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
|
||||
companion object {
|
||||
const val CHANNEL_ID = "cellguard"
|
||||
const val CHANNEL_ID = "cellguard_status_v2"
|
||||
private const val LEGACY_CHANNEL_ID = "cellguard"
|
||||
const val ALERT_CHANNEL_ID = "cellguard_alert"
|
||||
private const val NOTIF_ID = 0x0C6D
|
||||
private const val ALERT_NOTIF_ID = 0x0C6E
|
||||
private const val POLL_MS = 4_000L
|
||||
private const val NEIGHBOR_TTL_MS = 120_000L
|
||||
|
||||
private val ALERT_FMT = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user