Apply per-event notification logging rules
This commit is contained in:
+28
-6
@@ -8,6 +8,9 @@ import android.util.Log
|
||||
import se.ajpanton.notificationlog.data.EncryptedNotificationLogStore
|
||||
import se.ajpanton.notificationlog.model.NotificationAction
|
||||
import se.ajpanton.notificationlog.model.NotificationLogEntry
|
||||
import se.ajpanton.notificationlog.settings.LoggingRuleStore
|
||||
import se.ajpanton.notificationlog.settings.LoggingType
|
||||
import se.ajpanton.notificationlog.settings.NotificationRuleEvaluator
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@@ -15,10 +18,12 @@ class NotificationCaptureService : NotificationListenerService() {
|
||||
private val activeNotifications = mutableMapOf<String, NotificationSnapshot>()
|
||||
private lateinit var logStore: EncryptedNotificationLogStore
|
||||
private lateinit var writeExecutor: ExecutorService
|
||||
private lateinit var ruleStore: LoggingRuleStore
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
logStore = EncryptedNotificationLogStore(this)
|
||||
ruleStore = LoggingRuleStore(this)
|
||||
writeExecutor = Executors.newSingleThreadExecutor { runnable ->
|
||||
Thread(runnable, "notification-log-writer")
|
||||
}
|
||||
@@ -29,7 +34,7 @@ class NotificationCaptureService : NotificationListenerService() {
|
||||
getActiveNotifications()?.forEach { sbn ->
|
||||
val snapshot = NotificationContents.snapshot(sbn)
|
||||
activeNotifications[snapshot.key] = snapshot
|
||||
record(snapshot, NotificationAction.ALREADY_ACTIVE, includeContents = true)
|
||||
record(snapshot, NotificationAction.ALREADY_ACTIVE, LoggingType.APPEARING, includeContents = true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +42,9 @@ class NotificationCaptureService : NotificationListenerService() {
|
||||
val snapshot = NotificationContents.snapshot(sbn)
|
||||
val previous = activeNotifications.put(snapshot.key, snapshot)
|
||||
when {
|
||||
previous == null -> record(snapshot, NotificationAction.APPEARED, includeContents = true)
|
||||
previous.contents != snapshot.contents -> record(snapshot, NotificationAction.EDITED, includeContents = true)
|
||||
previous == null -> record(snapshot, NotificationAction.APPEARED, LoggingType.APPEARING, includeContents = true)
|
||||
previous.textContents != snapshot.textContents || previous.hasImage != snapshot.hasImage ->
|
||||
record(snapshot, NotificationAction.EDITED, LoggingType.EDITS, includeContents = true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +54,7 @@ class NotificationCaptureService : NotificationListenerService() {
|
||||
reason: Int,
|
||||
) {
|
||||
val snapshot = activeNotifications.remove(sbn.key) ?: NotificationContents.snapshot(sbn)
|
||||
record(snapshot, actionForRemoval(reason), includeContents = false)
|
||||
record(snapshot, actionForRemoval(reason), LoggingType.DISAPPEARING, includeContents = false)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@@ -56,14 +62,20 @@ class NotificationCaptureService : NotificationListenerService() {
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun record(snapshot: NotificationSnapshot, action: NotificationAction, includeContents: Boolean) {
|
||||
private fun record(
|
||||
snapshot: NotificationSnapshot,
|
||||
action: NotificationAction,
|
||||
loggingType: LoggingType,
|
||||
includeContents: Boolean,
|
||||
) {
|
||||
if (!NotificationRuleEvaluator.allows(ruleStore.ruleFor(loggingType), snapshot.packageName)) return
|
||||
val appName = appName(snapshot.packageName)
|
||||
val entry = NotificationLogEntry(
|
||||
recordedAtEpochMillis = System.currentTimeMillis(),
|
||||
packageName = snapshot.packageName,
|
||||
appName = appName,
|
||||
action = action,
|
||||
contents = if (includeContents) snapshot.contents else null,
|
||||
contents = if (includeContents) visibleContents(snapshot) else null,
|
||||
)
|
||||
writeExecutor.execute {
|
||||
try {
|
||||
@@ -74,6 +86,16 @@ class NotificationCaptureService : NotificationListenerService() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun visibleContents(snapshot: NotificationSnapshot): String? {
|
||||
val text = snapshot.textContents?.takeIf {
|
||||
NotificationRuleEvaluator.allows(ruleStore.ruleFor(LoggingType.TEXT_CONTENT), snapshot.packageName)
|
||||
}
|
||||
val image = snapshot.hasImage && NotificationRuleEvaluator.allows(
|
||||
ruleStore.ruleFor(LoggingType.IMAGE_CONTENT), snapshot.packageName,
|
||||
)
|
||||
return listOfNotNull(text, if (image) "[image]" else null).joinToString(" — ").ifEmpty { null }
|
||||
}
|
||||
|
||||
private fun appName(packageName: String): String = try {
|
||||
val applicationInfo = packageManager.getApplicationInfo(packageName, 0)
|
||||
packageManager.getApplicationLabel(applicationInfo).toString()
|
||||
|
||||
@@ -7,7 +7,8 @@ import android.service.notification.StatusBarNotification
|
||||
data class NotificationSnapshot(
|
||||
val key: String,
|
||||
val packageName: String,
|
||||
val contents: String?,
|
||||
val textContents: String?,
|
||||
val hasImage: Boolean,
|
||||
)
|
||||
|
||||
object NotificationContents {
|
||||
@@ -25,18 +26,16 @@ object NotificationContents {
|
||||
).forEach { message ->
|
||||
listOfNotNull(message.senderPerson?.name, message.text).joinToString(": ").addTo(parts)
|
||||
}
|
||||
if (extras.containsKey(Notification.EXTRA_PICTURE) ||
|
||||
extras.containsKey(Notification.EXTRA_PICTURE_ICON)
|
||||
) {
|
||||
"[image]".addTo(parts)
|
||||
}
|
||||
return parts.takeIf { it.isNotEmpty() }?.joinToString(" — ")
|
||||
}
|
||||
|
||||
fun snapshot(sbn: StatusBarNotification) = NotificationSnapshot(
|
||||
key = sbn.key,
|
||||
packageName = sbn.packageName,
|
||||
contents = extract(sbn.notification),
|
||||
textContents = extract(sbn.notification),
|
||||
hasImage = sbn.notification.extras?.let {
|
||||
it.containsKey(Notification.EXTRA_PICTURE) || it.containsKey(Notification.EXTRA_PICTURE_ICON)
|
||||
} == true,
|
||||
)
|
||||
|
||||
private fun CharSequence.addTo(parts: MutableSet<String>) {
|
||||
|
||||
Reference in New Issue
Block a user