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()