diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 31e3deb..2f48287 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ ) = synchronized(lock) { + val existing = readPayloadOrNull()?.let { NotificationLogEntryJson.decode(cipher.decrypt(it)) } ?: emptyList() write(entries) + removeUnreferencedImages(entries, existing) } fun clear() = synchronized(lock) { @@ -49,6 +51,12 @@ class EncryptedNotificationLogStore(context: Context) { true } + /** Removes failed-write remnants and images no remaining log entry references. */ + fun removeOrphanedImages() = synchronized(lock) { + val entries = readPayloadOrNull()?.let { NotificationLogEntryJson.decode(cipher.decrypt(it)) } ?: emptyList() + removeUnreferencedImages(entries) + } + private fun write(entries: List) { val payload = cipher.encrypt(NotificationLogEntryJson.encode(entries)) val output = file.startWrite() @@ -82,6 +90,26 @@ class EncryptedNotificationLogStore(context: Context) { if (id.matches(IMAGE_ID_PATTERN)) File(imageDirectory, "$id.bin").delete() } + private fun removeUnreferencedImages( + entries: List, + previousEntries: List = emptyList(), + ) { + val referenced = entries.mapNotNull { it.imageId }.toSet() + previousEntries.mapNotNull { it.imageId } + .filterNot(referenced::contains) + .forEach(::deleteImage) + imageDirectory.listFiles()?.forEach { image -> + val imageId = image.name.removeSuffix(".bin") + if (image.name.endsWith(".new") || + !image.name.endsWith(".bin") || + !imageId.matches(IMAGE_ID_PATTERN) || + imageId !in referenced + ) { + image.delete() + } + } + } + private fun readPayloadOrNull(): EncryptedPayload? { val input = try { file.openRead() diff --git a/app/src/main/java/se/ajpanton/notificationlog/settings/AppFilterSettings.kt b/app/src/main/java/se/ajpanton/notificationlog/settings/AppFilterSettings.kt index bea4590..7eef048 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/settings/AppFilterSettings.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/settings/AppFilterSettings.kt @@ -11,18 +11,14 @@ data class AppFilterSettings( ) class AppFilterSettingsStore(context: Context) { - private val appContext = context.applicationContext - private val preferences = appContext.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE) + private val preferences = context.applicationContext.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE) - fun load(): AppFilterSettings { - migrateLegacyFiltersIfNeeded() - return AppFilterSettings( + fun load(): AppFilterSettings = AppFilterSettings( mode = AppRuleMode.valueOf(preferences.getString(MODE, AppRuleMode.BLACKLIST.name)!!), selectedPackages = preferences.getStringSet(SELECTED_PACKAGES, emptySet())?.toSet() ?: emptySet(), onlySeenApps = preferences.getBoolean(ONLY_SEEN_APPS, false), seenAppsFirst = preferences.getBoolean(SEEN_APPS_FIRST, true), ) - } fun save(settings: AppFilterSettings) = preferences.edit { putString(MODE, settings.mode.name) @@ -31,21 +27,16 @@ class AppFilterSettingsStore(context: Context) { putBoolean(SEEN_APPS_FIRST, settings.seenAppsFirst) } - private fun migrateLegacyFiltersIfNeeded() { - if (preferences.getBoolean(MIGRATED, false)) return - val legacy = appContext.getSharedPreferences("logging-rules", Context.MODE_PRIVATE) - val blacklistedPackages = LoggingType.entries.flatMap { type -> - legacy.getStringSet("${type.name.lowercase()}.selected_packages", emptySet()).orEmpty() - .takeIf { legacy.getString("${type.name.lowercase()}.app_rule_mode", AppRuleMode.BLACKLIST.name) == AppRuleMode.BLACKLIST.name } - .orEmpty() - }.toSet() - save(AppFilterSettings(selectedPackages = blacklistedPackages)) - preferences.edit { putBoolean(MIGRATED, true) } + fun removeUninstalledPackages(installedPackages: Set) { + val settings = load() + val retainedPackages = settings.selectedPackages.intersect(installedPackages) + if (retainedPackages != settings.selectedPackages) { + save(settings.copy(selectedPackages = retainedPackages)) + } } private companion object { const val FILE_NAME = "app-filter-settings" - const val MIGRATED = "migrated_from_per_event_filters" const val MODE = "mode" const val SELECTED_PACKAGES = "selected_packages" const val ONLY_SEEN_APPS = "only_seen_apps" diff --git a/app/src/main/java/se/ajpanton/notificationlog/settings/PerAppEventSettingsStore.kt b/app/src/main/java/se/ajpanton/notificationlog/settings/PerAppEventSettingsStore.kt index e5a8435..3a90a6f 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/settings/PerAppEventSettingsStore.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/settings/PerAppEventSettingsStore.kt @@ -36,6 +36,16 @@ class PerAppEventSettingsStore(context: Context) { .values .any { value -> (value as? Set<*>)?.isNotEmpty() == true } + fun removeUninstalledPackages(installedPackages: Set) { + val removed = preferences.all.keys + .filter { it.startsWith(KEY_PREFIX) } + .filter { it.removePrefix(KEY_PREFIX) !in installedPackages } + if (removed.isNotEmpty()) { + preferences.edit { removed.forEach(::remove) } + updateListenerComponent() + } + } + private fun saveEvents(packageName: String, enabledEvents: Set) { preferences.edit { putStringSet(key(packageName), enabledEvents.mapTo(mutableSetOf()) { it.name }) } updateListenerComponent()