diff --git a/app/src/main/java/se/ajpanton/notificationlog/capture/NotificationCaptureService.kt b/app/src/main/java/se/ajpanton/notificationlog/capture/NotificationCaptureService.kt index 1c36d8c..79f6408 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/capture/NotificationCaptureService.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/capture/NotificationCaptureService.kt @@ -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) diff --git a/app/src/main/java/se/ajpanton/notificationlog/capture/NotificationSnapshot.kt b/app/src/main/java/se/ajpanton/notificationlog/capture/NotificationSnapshot.kt index f7f4a39..55326fa 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/capture/NotificationSnapshot.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/capture/NotificationSnapshot.kt @@ -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) { diff --git a/app/src/main/java/se/ajpanton/notificationlog/data/EncryptedImageStore.kt b/app/src/main/java/se/ajpanton/notificationlog/data/EncryptedImageStore.kt new file mode 100644 index 0000000..cc1be06 --- /dev/null +++ b/app/src/main/java/se/ajpanton/notificationlog/data/EncryptedImageStore.kt @@ -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) + } + } +}