Persist notification big pictures encrypted

This commit is contained in:
ajp_anton
2026-07-23 10:23:49 +00:00
parent 9e917eacd3
commit 8570f65d09
3 changed files with 33 additions and 0 deletions
@@ -6,6 +6,7 @@ import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import android.util.Log
import se.ajpanton.notificationlog.data.EncryptedNotificationLogStore
import se.ajpanton.notificationlog.data.EncryptedImageStore
import se.ajpanton.notificationlog.model.NotificationAction
import se.ajpanton.notificationlog.model.NotificationLogEntry
import se.ajpanton.notificationlog.settings.LoggingRuleStore
@@ -19,11 +20,13 @@ class NotificationCaptureService : NotificationListenerService() {
private lateinit var logStore: EncryptedNotificationLogStore
private lateinit var writeExecutor: ExecutorService
private lateinit var ruleStore: LoggingRuleStore
private lateinit var imageStore: EncryptedImageStore
override fun onCreate() {
super.onCreate()
logStore = EncryptedNotificationLogStore(this)
ruleStore = LoggingRuleStore(this)
imageStore = EncryptedImageStore(this)
writeExecutor = Executors.newSingleThreadExecutor { runnable ->
Thread(runnable, "notification-log-writer")
}
@@ -83,6 +86,9 @@ class NotificationCaptureService : NotificationListenerService() {
)
writeExecutor.execute {
try {
if (includeContents && snapshot.imageBytes != null && NotificationRuleEvaluator.allows(ruleStore.ruleFor(LoggingType.IMAGE_CONTENT), snapshot.packageName)) {
imageStore.save(entry.id, snapshot.imageBytes)
}
logStore.append(entry)
} catch (error: Exception) {
Log.e(TAG, "Could not persist notification log entry", error)
@@ -1,6 +1,7 @@
package se.ajpanton.notificationlog.capture
import android.app.Notification
import android.graphics.Bitmap
import android.os.Bundle
import android.service.notification.StatusBarNotification
@@ -10,6 +11,7 @@ data class NotificationSnapshot(
val textContents: String?,
val hasImage: Boolean,
val isRoutine: Boolean,
val imageBytes: ByteArray?,
)
object NotificationContents {
@@ -39,6 +41,12 @@ object NotificationContents {
} == true,
isRoutine = sbn.notification.extras?.getBoolean(Notification.EXTRA_SHOW_CHRONOMETER, false) == true ||
sbn.notification.extras?.containsKey(Notification.EXTRA_PROGRESS) == true,
imageBytes = sbn.notification.extras?.getParcelable(Notification.EXTRA_PICTURE, Bitmap::class.java)?.let { bitmap ->
java.io.ByteArrayOutputStream().use { output ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, output)
output.toByteArray()
}
},
)
private fun CharSequence.addTo(parts: MutableSet<String>) {
@@ -0,0 +1,19 @@
package se.ajpanton.notificationlog.data
import android.content.Context
import java.io.File
/** Stores copied notification image bytes independently of the source app's URI lifetime. */
class EncryptedImageStore(context: Context) {
private val directory = File(context.filesDir, "notification-images").also(File::mkdirs)
private val cipher = AesGcmCipher(LogEncryptionKeyProvider().getOrCreate())
fun save(id: String, bytes: ByteArray) {
val payload = cipher.encrypt(bytes)
File(directory, "$id.bin").outputStream().use { output ->
output.write(payload.initializationVector.size)
output.write(payload.initializationVector)
output.write(payload.cipherText)
}
}
}