From 4d09776277fee29ff227e94040e285427a976386 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Wed, 29 Apr 2026 12:12:13 -0400 Subject: [PATCH] Improve db usage around ensuring custom notification channel stae. --- .../contacts/sync/ContactDiscovery.kt | 12 +-- .../securesms/database/RecipientTable.kt | 12 ++- .../notifications/NotificationChannels.java | 79 +++++++++---------- .../notifications/VitalsViewModel.kt | 13 +-- 4 files changed, 59 insertions(+), 57 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt index b009219223..0e124090b7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt @@ -303,14 +303,10 @@ object ContactDiscovery { } if (NotificationChannels.supported()) { - SignalDatabase.recipients.getRecipientsWithNotificationChannels().use { reader -> - var recipient: Recipient? = reader.getNext() - - while (recipient != null) { - NotificationChannels.getInstance().updateContactChannelName(recipient) - recipient = reader.getNext() - } - } + SignalDatabase + .recipients + .getRecipientsWithNotificationChannels() + .forEach { NotificationChannels.getInstance().updateContactChannelName(Recipient.resolved(it.id)) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt index 7eb25c58b3..fb6db85537 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt @@ -731,9 +731,13 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da return RecipientReader(cursor) } - fun getRecipientsWithNotificationChannels(): RecipientReader { - val cursor = readableDatabase.query(TABLE_NAME, ID_PROJECTION, "$NOTIFICATION_CHANNEL NOT NULL", null, null, null, null) - return RecipientReader(cursor) + fun getRecipientsWithNotificationChannels(): List { + return readableDatabase + .select(ID, NOTIFICATION_CHANNEL) + .from(TABLE_NAME) + .where("$NOTIFICATION_CHANNEL NOT NULL") + .run() + .readToList { RecipientNotificationData(RecipientId.from(it.requireLong(ID)), it.requireNonNullString(NOTIFICATION_CHANNEL)) } } fun getExistingRecords(ids: Collection): Map { @@ -5027,4 +5031,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da val expiringProfileKeyCredential: Pair? = null, val clearUsername: Boolean = false ) + + data class RecipientNotificationData(val id: RecipientId, val channel: String) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java index eda1c7265a..70350b88d2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java @@ -175,7 +175,7 @@ public class NotificationChannels { /** * Whether or not notifications for the entire app are enabled. */ - public synchronized boolean areNotificationsEnabled() { + public boolean areNotificationsEnabled() { if (Build.VERSION.SDK_INT >= 24) { return ServiceUtil.getNotificationManager(context).areNotificationsEnabled(); } else { @@ -193,18 +193,16 @@ public class NotificationChannels { return; } - RecipientTable db = SignalDatabase.recipients(); - - try (RecipientTable.RecipientReader reader = db.getRecipientsWithNotificationChannels()) { - Recipient recipient; - while ((recipient = reader.getNext()) != null) { - NotificationManager notificationManager = ServiceUtil.getNotificationManager(context); - if (!channelExists(notificationManager.getNotificationChannel(recipient.getNotificationChannel()))) { - String id = createChannelFor(recipient); - db.setNotificationChannel(recipient.getId(), id); - } - } - } + NotificationManager notificationManager = ServiceUtil.getNotificationManager(context); + SignalDatabase.recipients() + .getRecipientsWithNotificationChannels() + .stream() + .forEach((info) -> { + if (!channelExists(notificationManager.getNotificationChannel(info.getChannel()))) { + String id = createChannelFor(Recipient.resolved(info.getId())); + SignalDatabase.recipients().setNotificationChannel(info.getId(), id); + } + }); ensureCustomChannelConsistency(); } @@ -465,7 +463,7 @@ public class NotificationChannels { * {@link #areNotificationsEnabled()} and {@link #isMessagesChannelGroupEnabled()} * to be safe. */ - public synchronized boolean isMessageChannelEnabled() { + public boolean isMessageChannelEnabled() { if (!supported()) { return true; } @@ -481,7 +479,7 @@ public class NotificationChannels { * a user could have blocked the specific channel, or notifications overall, and it'd still be * true. See {@link #isMessageChannelEnabled()} and {@link #areNotificationsEnabled()}. */ - public synchronized boolean isMessagesChannelGroupEnabled() { + public boolean isMessagesChannelGroupEnabled() { if (Build.VERSION.SDK_INT < 28) { return true; } @@ -492,7 +490,7 @@ public class NotificationChannels { return group != null && !group.isBlocked(); } - public synchronized boolean isCallsChannelValid() { + public boolean isCallsChannelValid() { if (!supported()) { return true; } @@ -561,18 +559,16 @@ public class NotificationChannels { } Log.d(TAG, "ensureCustomChannelConsistency()"); - NotificationManager notificationManager = ServiceUtil.getNotificationManager(context); - RecipientTable db = SignalDatabase.recipients(); - List customRecipients = new ArrayList<>(); - Set customChannelIds = new HashSet<>(); + NotificationManager notificationManager = ServiceUtil.getNotificationManager(context); + RecipientTable db = SignalDatabase.recipients(); + List customRecipients; + Set customChannelIds = new HashSet<>(); - try (RecipientTable.RecipientReader reader = db.getRecipientsWithNotificationChannels()) { - Recipient recipient; - while ((recipient = reader.getNext()) != null) { - customRecipients.add(recipient); - customChannelIds.add(recipient.getNotificationChannel()); - } - } + customRecipients = db.getRecipientsWithNotificationChannels(); + customRecipients.stream() + .forEach((info) -> { + customChannelIds.add(info.getChannel()); + }); Set existingChannelIds = notificationManager.getNotificationChannels().stream().map(NotificationChannel::getId).collect(Collectors.toSet()); @@ -602,9 +598,9 @@ public class NotificationChannels { } } - for (Recipient customRecipient : customRecipients) { - if (!existingChannelIds.contains(customRecipient.getNotificationChannel())) { - Log.i(TAG, "Consistency: Removing custom channel '"+ customRecipient.getNotificationChannel() + "' because the system doesn't have it."); + for (RecipientTable.RecipientNotificationData customRecipient : customRecipients) { + if (!existingChannelIds.contains(customRecipient.getChannel())) { + Log.i(TAG, "Consistency: Removing custom channel '"+ customRecipient.getChannel() + "' because the system doesn't have it."); db.setNotificationChannel(customRecipient.getId(), null); } } @@ -738,7 +734,11 @@ public class NotificationChannels { private static @NonNull String generateChannelIdFor(@NonNull Recipient recipient) { - return CONTACT_PREFIX + recipient.getId().serialize() + "_" + System.currentTimeMillis(); + return generateChannelIdFor(recipient.getId()); + } + + private static @NonNull String generateChannelIdFor(@NonNull RecipientId recipientId) { + return CONTACT_PREFIX + recipientId.serialize() + "_" + System.currentTimeMillis(); } @TargetApi(26) @@ -771,17 +771,14 @@ public class NotificationChannels { private void updateAllRecipientChannelLedColors(@NonNull NotificationManager notificationManager, @NonNull String color) { RecipientTable database = SignalDatabase.recipients(); - try (RecipientTable.RecipientReader recipients = database.getRecipientsWithNotificationChannels()) { - Recipient recipient; - while ((recipient = recipients.getNext()) != null) { - assert recipient.getNotificationChannel() != null; + database.getRecipientsWithNotificationChannels() + .stream() + .forEach((info) -> { + String newChannelId = generateChannelIdFor(info.getId()); + boolean success = updateExistingChannel(notificationManager, info.getChannel(), newChannelId, channel -> setLedPreference(channel, color)); - String newChannelId = generateChannelIdFor(recipient); - boolean success = updateExistingChannel(notificationManager, recipient.getNotificationChannel(), newChannelId, channel -> setLedPreference(channel, color)); - - database.setNotificationChannel(recipient.getId(), success ? newChannelId : null); - } - } + database.setNotificationChannel(info.getId(), success ? newChannelId : null); + }); ensureCustomChannelConsistency(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/VitalsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/VitalsViewModel.kt index 6e20fb4c9b..be6f1769a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/VitalsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/VitalsViewModel.kt @@ -53,23 +53,26 @@ class VitalsViewModel(private val context: Application) : AndroidViewModel(conte return Single.fromCallable { val deviceSpecificCondition = SlowNotificationHeuristics.getDeviceSpecificShowCondition() - if (deviceSpecificCondition == ShowCondition.ALWAYS && SlowNotificationHeuristics.shouldShowDeviceSpecificDialog()) { + val shouldShowDeviceSpecificDialog = SlowNotificationHeuristics.shouldShowDeviceSpecificDialog() + val havingDelayedNotifications = SlowNotificationHeuristics.isHavingDelayedNotifications() + + if (deviceSpecificCondition == ShowCondition.ALWAYS && shouldShowDeviceSpecificDialog) { return@fromCallable State.PROMPT_SPECIFIC_BATTERY_SAVER_DIALOG } - if (deviceSpecificCondition == ShowCondition.HAS_BATTERY_OPTIMIZATION_ON && SlowNotificationHeuristics.shouldShowDeviceSpecificDialog() && SlowNotificationHeuristics.isBatteryOptimizationsOn()) { + if (deviceSpecificCondition == ShowCondition.HAS_BATTERY_OPTIMIZATION_ON && shouldShowDeviceSpecificDialog && SlowNotificationHeuristics.isBatteryOptimizationsOn()) { return@fromCallable State.PROMPT_SPECIFIC_BATTERY_SAVER_DIALOG } - if (deviceSpecificCondition == ShowCondition.HAS_SLOW_NOTIFICATIONS && SlowNotificationHeuristics.shouldShowDeviceSpecificDialog() && SlowNotificationHeuristics.isHavingDelayedNotifications()) { + if (deviceSpecificCondition == ShowCondition.HAS_SLOW_NOTIFICATIONS && shouldShowDeviceSpecificDialog && havingDelayedNotifications) { return@fromCallable State.PROMPT_SPECIFIC_BATTERY_SAVER_DIALOG } - if (SlowNotificationHeuristics.isHavingDelayedNotifications() && SlowNotificationHeuristics.shouldPromptBatterySaver()) { + if (havingDelayedNotifications && SlowNotificationHeuristics.shouldPromptBatterySaver()) { return@fromCallable State.PROMPT_GENERAL_BATTERY_SAVER_DIALOG } - if (SlowNotificationHeuristics.isHavingDelayedNotifications() && SlowNotificationHeuristics.shouldPromptUserForDelayedNotificationLogs()) { + if (havingDelayedNotifications && SlowNotificationHeuristics.shouldPromptUserForDelayedNotificationLogs()) { return@fromCallable State.PROMPT_DEBUGLOGS_FOR_NOTIFICATIONS }