From 6156ee0534bf6f0dbb164adb648565e3b4a49011 Mon Sep 17 00:00:00 2001 From: d2weber <29163905+d2weber@users.noreply.github.com> Date: Wed, 3 Jun 2026 19:51:56 +0200 Subject: [PATCH 01/12] feat: use message style notifications With MessagingStyle, Notifications can be expanded to show more text of the message than just a single line. This feature has been requested in https://support.delta.chat/t/swipe-down-on-notification-to-expand-it/2742 --- CHANGELOG.md | 1 + .../notifications/NotificationCenter.java | 40 +++++++++++++------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b6ab92dd..95a3cd0ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * Fix: avoid crashes in Media preview sometimes +* Use message style notifications for longer message previews ## 2.51.0 2026-05 diff --git a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java index d5c3ccf20..888a749dc 100644 --- a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java +++ b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java @@ -23,8 +23,10 @@ import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; +import androidx.core.app.Person; import androidx.core.app.RemoteInput; import androidx.core.app.TaskStackBuilder; +import androidx.core.graphics.drawable.IconCompat; import com.b44t.messenger.DcChat; import com.b44t.messenger.DcContact; import com.b44t.messenger.DcContext; @@ -414,10 +416,6 @@ public class NotificationCenter { privacy.isDisplayMessage() ? dcMsg.getSummarytext(2000) : context.getString(R.string.notify_new_message); - if (dcChat.isMultiUser() && privacy.isDisplayContact()) { - shortLine = - dcMsg.getSenderName(dcContext.getContact(dcMsg.getFromId())) + ": " + shortLine; - } String tickerLine = shortLine; if (!dcChat.isMultiUser() && privacy.isDisplayContact()) { tickerLine = @@ -643,7 +641,7 @@ public class NotificationCenter { // Set avatar if (privacy.isDisplayContact()) { - Bitmap bitmap = getAvatar(dcChat); + Bitmap bitmap = getAvatar(new Recipient(context, dcChat)); if (bitmap != null) { builder.setLargeIcon(bitmap); } @@ -694,14 +692,29 @@ public class NotificationCenter { } } - // Create inbox style (gets visible if the notification is expanded) + // Create messaging style if (privacy.isDisplayContact() && privacy.isDisplayMessage() && messagesForInbox != null) { try { - NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); - for (String line : messagesForInbox.values()) { - inboxStyle.addLine(line); + DcContact selfContact = dcContext.getContact(DcContact.DC_CONTACT_ID_SELF); + Person self = + new Person.Builder() + .setName(selfContact.getDisplayName()) + .setIcon(getAvatarIcon(selfContact)) + .build(); + NotificationCompat.MessagingStyle style = new NotificationCompat.MessagingStyle(self); + style.setGroupConversation(dcChat.isMultiUser()); + style.setConversationTitle(dcChat.getName()); + for (Map.Entry msgEntry : messagesForInbox.entrySet()) { + DcMsg msg = dcContext.getMsg(msgEntry.getKey()); + DcContact senderContact = dcContext.getContact(msg.getFromId()); + Person sender = + new Person.Builder() + .setName(msg.getSenderName(senderContact)) + .setIcon(getAvatarIcon(senderContact)) + .build(); + style.addMessage(msgEntry.getValue(), msg.getSortTimestamp(), sender); } - builder.setStyle(inboxStyle); + builder.setStyle(style); } catch (Exception e) { Log.w(TAG, e); } @@ -797,8 +810,11 @@ public class NotificationCenter { } } - public Bitmap getAvatar(DcChat dcChat) { - Recipient recipient = new Recipient(context, dcChat); + public IconCompat getAvatarIcon(DcContact contact) { + return IconCompat.createWithBitmap(getAvatar(new Recipient(context, contact))); + } + + public Bitmap getAvatar(Recipient recipient) { try { Drawable drawable; ContactPhoto contactPhoto = recipient.getContactPhoto(context); From 8a3d29fca3df88814ac804abc71ea73464f3384a Mon Sep 17 00:00:00 2001 From: d2weber <29163905+d2weber@users.noreply.github.com> Date: Wed, 3 Jun 2026 22:06:15 +0200 Subject: [PATCH 02/12] fixup: handle null from getAvatar() --- .../securesms/notifications/NotificationCenter.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java index 888a749dc..650e45e10 100644 --- a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java +++ b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java @@ -810,11 +810,12 @@ public class NotificationCenter { } } - public IconCompat getAvatarIcon(DcContact contact) { - return IconCompat.createWithBitmap(getAvatar(new Recipient(context, contact))); + public @Nullable IconCompat getAvatarIcon(DcContact contact) { + Bitmap avatar = getAvatar(new Recipient(context, contact)); + return avatar != null ? IconCompat.createWithBitmap(avatar) : null; } - public Bitmap getAvatar(Recipient recipient) { + public @Nullable Bitmap getAvatar(Recipient recipient) { try { Drawable drawable; ContactPhoto contactPhoto = recipient.getContactPhoto(context); From d5edcea5d06de1d39a8eb117b1e82a70cf0c1bf9 Mon Sep 17 00:00:00 2001 From: d2weber <29163905+d2weber@users.noreply.github.com> Date: Wed, 3 Jun 2026 22:07:32 +0200 Subject: [PATCH 03/12] fixup: convert timestamp to ms --- .../securesms/notifications/NotificationCenter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java index 650e45e10..975d30f19 100644 --- a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java +++ b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java @@ -712,7 +712,7 @@ public class NotificationCenter { .setName(msg.getSenderName(senderContact)) .setIcon(getAvatarIcon(senderContact)) .build(); - style.addMessage(msgEntry.getValue(), msg.getSortTimestamp(), sender); + style.addMessage(msgEntry.getValue(), msg.getSortTimestamp() * 1000, sender); } builder.setStyle(style); } catch (Exception e) { From c3b7e06b86ded2675d56e5c2ca789276c4d804ef Mon Sep 17 00:00:00 2001 From: d2weber <29163905+d2weber@users.noreply.github.com> Date: Wed, 3 Jun 2026 22:15:58 +0200 Subject: [PATCH 04/12] add isBot to person (not sure if it has any effect) --- .../thoughtcrime/securesms/notifications/NotificationCenter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java index 975d30f19..3a3cc22e0 100644 --- a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java +++ b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java @@ -711,6 +711,7 @@ public class NotificationCenter { new Person.Builder() .setName(msg.getSenderName(senderContact)) .setIcon(getAvatarIcon(senderContact)) + .setBot(senderContact.isBot()) .build(); style.addMessage(msgEntry.getValue(), msg.getSortTimestamp() * 1000, sender); } From f8ec6d2da255e830182730bad400a94cbd02f6fa Mon Sep 17 00:00:00 2001 From: d2weber <29163905+d2weber@users.noreply.github.com> Date: Thu, 4 Jun 2026 11:09:24 +0200 Subject: [PATCH 05/12] fixup: reactions and webxdc updates --- .../notifications/NotificationCenter.java | 106 +++++++++++------- 1 file changed, 66 insertions(+), 40 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java index 3a3cc22e0..db2f58cb2 100644 --- a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java +++ b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java @@ -63,8 +63,18 @@ public class NotificationCenter { private volatile long lastAudibleNotification = 0; private static final long MIN_AUDIBLE_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(2); - // Map, contains the last lines of each chat for each account - private final HashMap>> inboxes = + private static class NotifData { + final Person sender; + final String text; + + NotifData(Person sender, String text) { + this.sender = sender; + this.text = text; + } + } + + // notification history of each chat for each account + private final HashMap>> inboxes = new HashMap<>(); public NotificationCenter(Context context) { @@ -412,27 +422,31 @@ public class NotificationCenter { DcMsg dcMsg = dcContext.getMsg(msgId); NotificationPrivacyPreference privacy = Prefs.getNotificationPrivacy(context); + DcContact sender = dcContext.getContact(dcMsg.getFromId()); + String senderName = dcMsg.getSenderName(sender); String shortLine = privacy.isDisplayMessage() ? dcMsg.getSummarytext(2000) : context.getString(R.string.notify_new_message); String tickerLine = shortLine; - if (!dcChat.isMultiUser() && privacy.isDisplayContact()) { - tickerLine = - dcMsg.getSenderName(dcContext.getContact(dcMsg.getFromId())) + ": " + tickerLine; - if (dcMsg.getOverrideSenderName() != null) { - // There is an "overridden" display name on the message, so, we need to prepend the - // display name to the message, - // i.e. set the shortLine to be the same as the tickerLine. - shortLine = tickerLine; - } + NotifData notifData = + new NotifData( + new Person.Builder() + .setName(senderName) + .setIcon(getAvatarIcon(sender)) + .setBot(sender.isBot()) + .build(), + shortLine); + + if (!dcChat.isMultiUser() && privacy.isDisplayContact()) { + tickerLine = senderName + ": " + tickerLine; } DcMsg quotedMsg = dcMsg.getQuotedMsg(); boolean isMention = dcChat.isMultiUser() && quotedMsg != null && quotedMsg.isOutgoing(); - maybeAddNotification(accountId, dcChat, msgId, shortLine, tickerLine, true, isMention); + maybeAddNotification(accountId, dcChat, msgId, notifData, tickerLine, true, isMention); }); } @@ -449,6 +463,7 @@ public class NotificationCenter { } DcContact sender = dcContext.getContact(contactId); + String senderName = dcMsg.getSenderName(sender); String shortLine = context.getString( R.string.reaction_by_other, @@ -456,8 +471,18 @@ public class NotificationCenter { reaction, dcMsg.getSummarytext(2000)); DcChat dcChat = dcContext.getChat(dcMsg.getChatId()); + + NotifData notifData = + new NotifData( + new Person.Builder() + .setName(senderName) + .setIcon(getAvatarIcon(sender)) + .setBot(sender.isBot()) + .build(), + shortLine); + maybeAddNotification( - accountId, dcChat, msgId, shortLine, shortLine, false, dcChat.isMultiUser()); + accountId, dcChat, msgId, notifData, shortLine, false, dcChat.isMultiUser()); }); } @@ -471,6 +496,7 @@ public class NotificationCenter { DcContext dcContext = context.getDcAccounts().getAccount(accountId); DcMsg dcMsg = dcContext.getMsg(msgId); + DcChat dcChat = dcContext.getChat(dcMsg.getChatId()); DcMsg parentMsg; if (dcMsg.getType() == DcMsg.DC_MSG_WEBXDC) { parentMsg = dcMsg; @@ -485,9 +511,15 @@ public class NotificationCenter { JSONObject info = parentMsg.getWebxdcInfo(); final String name = JsonUtils.optString(info, "name"); String shortLine = name.isEmpty() ? text : (name + ": " + text); - DcChat dcChat = dcContext.getChat(dcMsg.getChatId()); + NotifData notifData = + new NotifData( + new Person.Builder() + .setIcon( + IconCompat.createWithBitmap(getAvatar(new Recipient(context, dcChat)))) + .build(), + shortLine); maybeAddNotification( - accountId, dcChat, msgId, shortLine, shortLine, false, dcChat.isMultiUser()); + accountId, dcChat, msgId, notifData, shortLine, false, dcChat.isMultiUser()); }); } @@ -496,7 +528,7 @@ public class NotificationCenter { int accountId, DcChat dcChat, int msgId, - String shortLine, + NotifData notifData, String tickerLine, boolean playInChatSound, boolean isMention) { @@ -536,20 +568,20 @@ public class NotificationCenter { // the user may eg. have chosen a different sound String notificationChannel = getNotificationChannel(notificationManager, chatData, dcChat); - LinkedHashMap messagesForInbox = null; + LinkedHashMap messagesForInbox = null; if (privacy.isDisplayContact() && privacy.isDisplayMessage()) { synchronized (inboxes) { - HashMap> accountInbox = inboxes.get(accountId); + HashMap> accountInbox = inboxes.get(accountId); if (accountInbox == null) { accountInbox = new HashMap<>(); inboxes.put(accountId, accountInbox); } - LinkedHashMap messages = accountInbox.get(chatId); + LinkedHashMap messages = accountInbox.get(chatId); if (messages == null) { messages = new LinkedHashMap<>(); accountInbox.put(chatId, messages); } - messages.put(msgId, shortLine); + messages.put(msgId, notifData); messagesForInbox = new LinkedHashMap<>(messages); } } @@ -562,7 +594,7 @@ public class NotificationCenter { dcContext, dcChat, notificationChannel, - shortLine, + notifData.text, tickerLine, signal, messagesForInbox, @@ -581,7 +613,7 @@ public class NotificationCenter { String contentText, String ticker, boolean signal, - LinkedHashMap messagesForInbox, + LinkedHashMap messagesForInbox, int messageCount, boolean includeSummary) { try { @@ -704,16 +736,10 @@ public class NotificationCenter { NotificationCompat.MessagingStyle style = new NotificationCompat.MessagingStyle(self); style.setGroupConversation(dcChat.isMultiUser()); style.setConversationTitle(dcChat.getName()); - for (Map.Entry msgEntry : messagesForInbox.entrySet()) { - DcMsg msg = dcContext.getMsg(msgEntry.getKey()); - DcContact senderContact = dcContext.getContact(msg.getFromId()); - Person sender = - new Person.Builder() - .setName(msg.getSenderName(senderContact)) - .setIcon(getAvatarIcon(senderContact)) - .setBot(senderContact.isBot()) - .build(); - style.addMessage(msgEntry.getValue(), msg.getSortTimestamp() * 1000, sender); + for (Map.Entry msgEntry : messagesForInbox.entrySet()) { + long timestamp_ms = dcContext.getMsg(msgEntry.getKey()).getSortTimestamp() * 1000; + NotifData notifData = msgEntry.getValue(); + style.addMessage(notifData.text, timestamp_ms, notifData.sender); } builder.setStyle(style); } catch (Exception e) { @@ -762,7 +788,7 @@ public class NotificationCenter { @WorkerThread private void rebuildNotification( - int accountId, int chatId, LinkedHashMap messages) { + int accountId, int chatId, LinkedHashMap messages) { try { DcContext dcContext = ApplicationContext.getDcAccounts().getAccount(accountId); DcChat dcChat = dcContext.getChat(chatId); @@ -780,9 +806,9 @@ public class NotificationCenter { // Get the latest message ID (last entry in LinkedHashMap) Integer latestMsgId = null; String lastLine = null; - for (Map.Entry entry : messages.entrySet()) { + for (Map.Entry entry : messages.entrySet()) { latestMsgId = entry.getKey(); - lastLine = entry.getValue(); + lastLine = entry.getValue().text; } if (latestMsgId == null || lastLine == null) { return; @@ -855,12 +881,12 @@ public class NotificationCenter { public void removeNotification(int accountId, int chatId, int msgId) { boolean shouldCancelNotification = false; boolean removeSummary = false; - LinkedHashMap remainingMessages = null; + LinkedHashMap remainingMessages = null; synchronized (inboxes) { - HashMap> accountInbox = inboxes.get(accountId); + HashMap> accountInbox = inboxes.get(accountId); if (accountInbox != null) { - LinkedHashMap messages = accountInbox.get(chatId); + LinkedHashMap messages = accountInbox.get(chatId); if (messages != null) { messages.remove(msgId); @@ -893,7 +919,7 @@ public class NotificationCenter { public void removeNotifications(int accountId, int chatId) { boolean removeSummary; synchronized (inboxes) { - HashMap> accountInbox = inboxes.get(accountId); + HashMap> accountInbox = inboxes.get(accountId); if (accountInbox == null) { accountInbox = new HashMap<>(); } @@ -919,7 +945,7 @@ public class NotificationCenter { NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); String tag = String.valueOf(accountId); synchronized (inboxes) { - HashMap> accountInbox = inboxes.get(accountId); + HashMap> accountInbox = inboxes.get(accountId); notificationManager.cancel(tag, ID_MSG_SUMMARY); if (accountInbox != null) { for (Integer chatId : accountInbox.keySet()) { From d0a584d15d98b1e6aa0cf5dd406ffe06bf50cbe4 Mon Sep 17 00:00:00 2001 From: d2weber <29163905+d2weber@users.noreply.github.com> Date: Thu, 4 Jun 2026 11:36:53 +0200 Subject: [PATCH 06/12] fixup: cleanup getAvatar --- .../notifications/NotificationCenter.java | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java index db2f58cb2..f18c64226 100644 --- a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java +++ b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java @@ -512,12 +512,7 @@ public class NotificationCenter { final String name = JsonUtils.optString(info, "name"); String shortLine = name.isEmpty() ? text : (name + ": " + text); NotifData notifData = - new NotifData( - new Person.Builder() - .setIcon( - IconCompat.createWithBitmap(getAvatar(new Recipient(context, dcChat)))) - .build(), - shortLine); + new NotifData(new Person.Builder().setIcon(getAvatarIcon(dcChat)).build(), shortLine); maybeAddNotification( accountId, dcChat, msgId, notifData, shortLine, false, dcChat.isMultiUser()); }); @@ -673,7 +668,7 @@ public class NotificationCenter { // Set avatar if (privacy.isDisplayContact()) { - Bitmap bitmap = getAvatar(new Recipient(context, dcChat)); + Bitmap bitmap = getAvatar(dcChat); if (bitmap != null) { builder.setLargeIcon(bitmap); } @@ -837,12 +832,25 @@ public class NotificationCenter { } } - public @Nullable IconCompat getAvatarIcon(DcContact contact) { - Bitmap avatar = getAvatar(new Recipient(context, contact)); + private @Nullable IconCompat getAvatarIcon(DcChat dcChat) { + Bitmap avatar = getAvatar(dcChat); return avatar != null ? IconCompat.createWithBitmap(avatar) : null; } - public @Nullable Bitmap getAvatar(Recipient recipient) { + private @Nullable IconCompat getAvatarIcon(DcContact dcContact) { + Bitmap avatar = getAvatar(dcContact); + return avatar != null ? IconCompat.createWithBitmap(avatar) : null; + } + + private @Nullable Bitmap getAvatar(DcChat dcChat) { + return getAvatar(new Recipient(context, dcChat)); + } + + private @Nullable Bitmap getAvatar(DcContact dcContact) { + return getAvatar(new Recipient(context, dcContact)); + } + + private @Nullable Bitmap getAvatar(Recipient recipient) { try { Drawable drawable; ContactPhoto contactPhoto = recipient.getContactPhoto(context); From d757ef59c0821d218bed093b5c095a500b00b202 Mon Sep 17 00:00:00 2001 From: d2weber <29163905+d2weber@users.noreply.github.com> Date: Thu, 4 Jun 2026 18:12:03 +0200 Subject: [PATCH 07/12] fixup: displayContact without displayMessage --- .../securesms/notifications/NotificationCenter.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java index f18c64226..45c3f26b0 100644 --- a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java +++ b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java @@ -428,6 +428,10 @@ public class NotificationCenter { privacy.isDisplayMessage() ? dcMsg.getSummarytext(2000) : context.getString(R.string.notify_new_message); + if (dcChat.isMultiUser() && privacy.isDisplayContact()) { + shortLine = + dcMsg.getSenderName(dcContext.getContact(dcMsg.getFromId())) + ": " + shortLine; + } String tickerLine = shortLine; NotifData notifData = From 46be278bf5a6d7c272ce97a239537fe0f23e4228 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Tue, 9 Jun 2026 17:58:01 +0200 Subject: [PATCH 08/12] update ndk version --- .github/workflows/cache-core.yml | 2 +- .github/workflows/preview-apk.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cache-core.yml b/.github/workflows/cache-core.yml index 7c7b20df7..3418fe1d0 100644 --- a/.github/workflows/cache-core.yml +++ b/.github/workflows/cache-core.yml @@ -25,7 +25,7 @@ jobs: - uses: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1.6.0 id: setup-ndk with: - ndk-version: r27 + ndk-version: "r29" - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 with: diff --git a/.github/workflows/preview-apk.yml b/.github/workflows/preview-apk.yml index 4cf14f6f4..767f5491d 100644 --- a/.github/workflows/preview-apk.yml +++ b/.github/workflows/preview-apk.yml @@ -42,7 +42,7 @@ jobs: - uses: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1.6.0 id: setup-ndk with: - ndk-version: r27 + ndk-version: "r29" - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 with: From f0760d66953f20e48fc5144f474777c0b99e0721 Mon Sep 17 00:00:00 2001 From: d2weber <29163905+d2weber@users.noreply.github.com> Date: Tue, 9 Jun 2026 20:46:28 +0200 Subject: [PATCH 09/12] make notification icon nice and big in all cases --- .../notifications/NotificationCenter.java | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java index 45c3f26b0..9332fca3f 100644 --- a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java +++ b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java @@ -26,6 +26,8 @@ import androidx.core.app.NotificationManagerCompat; import androidx.core.app.Person; import androidx.core.app.RemoteInput; import androidx.core.app.TaskStackBuilder; +import androidx.core.content.pm.ShortcutInfoCompat; +import androidx.core.content.pm.ShortcutManagerCompat; import androidx.core.graphics.drawable.IconCompat; import com.b44t.messenger.DcChat; import com.b44t.messenger.DcContact; @@ -44,6 +46,7 @@ import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ConversationActivity; import org.thoughtcrime.securesms.ConversationListActivity; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.ShareActivity; import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference; @@ -440,6 +443,7 @@ public class NotificationCenter { .setName(senderName) .setIcon(getAvatarIcon(sender)) .setBot(sender.isBot()) + .setKey(dcContext.getAccountId() + "-" + sender.getId()) .build(), shortLine); @@ -726,6 +730,21 @@ public class NotificationCenter { // Create messaging style if (privacy.isDisplayContact() && privacy.isDisplayMessage() && messagesForInbox != null) { try { + Intent viewChatIntent = new Intent(context, ShareActivity.class); + viewChatIntent.setAction(Intent.ACTION_SEND); + viewChatIntent.putExtra(ShareActivity.EXTRA_ACC_ID, dcContext.getAccountId()); + viewChatIntent.putExtra(ShareActivity.EXTRA_CHAT_ID, dcChat.getId()); + + ShortcutInfoCompat shortcut = + new ShortcutInfoCompat.Builder( + context, "chat-" + dcContext.getAccountId() + "-" + dcChat.getId()) + .setShortLabel(dcChat.getName()) + .setLongLived(true) + .setIntent(viewChatIntent) + .build(); + ShortcutManagerCompat.pushDynamicShortcut(context, shortcut); + builder.setShortcutInfo(shortcut); + DcContact selfContact = dcContext.getContact(DcContact.DC_CONTACT_ID_SELF); Person self = new Person.Builder() @@ -733,8 +752,10 @@ public class NotificationCenter { .setIcon(getAvatarIcon(selfContact)) .build(); NotificationCompat.MessagingStyle style = new NotificationCompat.MessagingStyle(self); - style.setGroupConversation(dcChat.isMultiUser()); - style.setConversationTitle(dcChat.getName()); + if (dcChat.isMultiUser()) { + style.setGroupConversation(true); + style.setConversationTitle(dcChat.getName()); + } for (Map.Entry msgEntry : messagesForInbox.entrySet()) { long timestamp_ms = dcContext.getMsg(msgEntry.getKey()).getSortTimestamp() * 1000; NotifData notifData = msgEntry.getValue(); From 8c1bc8e70e98dd822bb35b2a06c1db9b9c44a53f Mon Sep 17 00:00:00 2001 From: d2weber <29163905+d2weber@users.noreply.github.com> Date: Wed, 10 Jun 2026 10:25:58 +0200 Subject: [PATCH 10/12] apply messaging style also if !isDisplayMessage() --- .../notifications/NotificationCenter.java | 74 ++++++++++++++----- 1 file changed, 57 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java index 9332fca3f..d5dea0ac4 100644 --- a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java +++ b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java @@ -11,6 +11,7 @@ import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.media.AudioAttributes; import android.media.RingtoneManager; @@ -34,6 +35,7 @@ import com.b44t.messenger.DcContact; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcMsg; import com.bumptech.glide.load.engine.DiskCacheStrategy; +import java.io.ByteArrayInputStream; import java.math.BigInteger; import java.security.MessageDigest; import java.util.HashMap; @@ -427,15 +429,14 @@ public class NotificationCenter { DcContact sender = dcContext.getContact(dcMsg.getFromId()); String senderName = dcMsg.getSenderName(sender); - String shortLine = + String text = privacy.isDisplayMessage() ? dcMsg.getSummarytext(2000) : context.getString(R.string.notify_new_message); + String shortLine = text; if (dcChat.isMultiUser() && privacy.isDisplayContact()) { - shortLine = - dcMsg.getSenderName(dcContext.getContact(dcMsg.getFromId())) + ": " + shortLine; + shortLine = senderName + ": " + text; } - String tickerLine = shortLine; NotifData notifData = new NotifData( @@ -443,12 +444,13 @@ public class NotificationCenter { .setName(senderName) .setIcon(getAvatarIcon(sender)) .setBot(sender.isBot()) - .setKey(dcContext.getAccountId() + "-" + sender.getId()) + .setKey(accountId + "-" + sender.getId()) .build(), - shortLine); + text); - if (!dcChat.isMultiUser() && privacy.isDisplayContact()) { - tickerLine = senderName + ": " + tickerLine; + String tickerLine = text; + if (privacy.isDisplayContact()) { + tickerLine = senderName + ": " + text; } DcMsg quotedMsg = dcMsg.getQuotedMsg(); @@ -472,7 +474,7 @@ public class NotificationCenter { DcContact sender = dcContext.getContact(contactId); String senderName = dcMsg.getSenderName(sender); - String shortLine = + String text = context.getString( R.string.reaction_by_other, sender.getDisplayName(), @@ -486,11 +488,12 @@ public class NotificationCenter { .setName(senderName) .setIcon(getAvatarIcon(sender)) .setBot(sender.isBot()) + .setKey(accountId + "-" + sender.getId()) .build(), - shortLine); + text); maybeAddNotification( - accountId, dcChat, msgId, notifData, shortLine, false, dcChat.isMultiUser()); + accountId, dcChat, msgId, notifData, text, false, dcChat.isMultiUser()); }); } @@ -518,11 +521,34 @@ public class NotificationCenter { JSONObject info = parentMsg.getWebxdcInfo(); final String name = JsonUtils.optString(info, "name"); - String shortLine = name.isEmpty() ? text : (name + ": " + text); - NotifData notifData = - new NotifData(new Person.Builder().setIcon(getAvatarIcon(dcChat)).build(), shortLine); + String tickerLine = name.isEmpty() ? text : (name + ": " + text); + + NotifData notifData; + if (dcChat.isMultiUser()) { + byte[] blob = parentMsg.getWebxdcBlob(JsonUtils.optString(info, "icon")); + notifData = + new NotifData( + new Person.Builder() + .setName(name) + .setIcon(getAvatarIcon(blob)) + .setKey(accountId + "-webxdc-" + msgId) + .build(), + text); + } else { + DcContact sender = dcContext.getContact(contactId); + notifData = + new NotifData( + new Person.Builder() + .setName(dcMsg.getSenderName(sender)) + .setIcon(getAvatarIcon(sender)) + .setBot(sender.isBot()) + .setKey(dcContext.getAccountId() + "-" + sender.getId()) + .build(), + tickerLine); + } + maybeAddNotification( - accountId, dcChat, msgId, notifData, shortLine, false, dcChat.isMultiUser()); + accountId, dcChat, msgId, notifData, tickerLine, false, dcChat.isMultiUser()); }); } @@ -572,7 +598,7 @@ public class NotificationCenter { String notificationChannel = getNotificationChannel(notificationManager, chatData, dcChat); LinkedHashMap messagesForInbox = null; - if (privacy.isDisplayContact() && privacy.isDisplayMessage()) { + if (privacy.isDisplayContact()) { synchronized (inboxes) { HashMap> accountInbox = inboxes.get(accountId); if (accountInbox == null) { @@ -584,6 +610,9 @@ public class NotificationCenter { messages = new LinkedHashMap<>(); accountInbox.put(chatId, messages); } + if (!privacy.isDisplayMessage()) { + messages.clear(); + } messages.put(msgId, notifData); messagesForInbox = new LinkedHashMap<>(messages); } @@ -728,7 +757,7 @@ public class NotificationCenter { } // Create messaging style - if (privacy.isDisplayContact() && privacy.isDisplayMessage() && messagesForInbox != null) { + if (privacy.isDisplayContact() && messagesForInbox != null) { try { Intent viewChatIntent = new Intent(context, ShareActivity.class); viewChatIntent.setAction(Intent.ACTION_SEND); @@ -750,6 +779,7 @@ public class NotificationCenter { new Person.Builder() .setName(selfContact.getDisplayName()) .setIcon(getAvatarIcon(selfContact)) + .setKey(accountId + "-" + selfContact.getId()) .build(); NotificationCompat.MessagingStyle style = new NotificationCompat.MessagingStyle(self); if (dcChat.isMultiUser()) { @@ -857,6 +887,16 @@ public class NotificationCenter { } } + private static @Nullable IconCompat getAvatarIcon(byte[] blob) { + if (blob == null) { + return null; + } + ByteArrayInputStream is = new ByteArrayInputStream(blob); + BitmapDrawable drawable = (BitmapDrawable) Drawable.createFromStream(is, "icon"); + Bitmap bitmap = drawable.getBitmap(); + return IconCompat.createWithBitmap(bitmap); + } + private @Nullable IconCompat getAvatarIcon(DcChat dcChat) { Bitmap avatar = getAvatar(dcChat); return avatar != null ? IconCompat.createWithBitmap(avatar) : null; From 80de08f9809ced9be52910ec6f0b31a367bc4457 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 11 Jun 2026 15:07:48 +0200 Subject: [PATCH 11/12] remove remainings of is_chatmail usage instead, force_ncryption is checked now, and this only for for option "New Chat -> New Email". this also fixes the bug that, for classic relays, by default "force encryption" is set but we still show "new email". what's left for now is a check for is_chatmail whether fcm is sufficient or not. --- src/main/java/com/b44t/messenger/DcAccounts.java | 2 +- src/main/java/com/b44t/messenger/DcContext.java | 4 ---- .../org/thoughtcrime/securesms/connect/DcContactsLoader.java | 4 ++-- src/main/java/org/thoughtcrime/securesms/qr/QrActivity.java | 5 ++++- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/b44t/messenger/DcAccounts.java b/src/main/java/com/b44t/messenger/DcAccounts.java index d0bc07895..ba90170c6 100644 --- a/src/main/java/com/b44t/messenger/DcAccounts.java +++ b/src/main/java/com/b44t/messenger/DcAccounts.java @@ -74,7 +74,7 @@ public class DcAccounts { public boolean isAllChatmail() { for (int accountId : getAll()) { DcContext dcContext = getAccount(accountId); - if (!dcContext.isChatmail()) { + if (dcContext.getConfigInt("is_chatmail") == 0) { return false; } } diff --git a/src/main/java/com/b44t/messenger/DcContext.java b/src/main/java/com/b44t/messenger/DcContext.java index d972001b9..980f44ce6 100644 --- a/src/main/java/com/b44t/messenger/DcContext.java +++ b/src/main/java/com/b44t/messenger/DcContext.java @@ -344,10 +344,6 @@ public class DcContext { return displayname; } - public boolean isChatmail() { - return getConfigInt("is_chatmail") == 1; - } - public boolean isMuted() { return getConfigInt("is_muted") == 1; } diff --git a/src/main/java/org/thoughtcrime/securesms/connect/DcContactsLoader.java b/src/main/java/org/thoughtcrime/securesms/connect/DcContactsLoader.java index 62d6c1392..afffc8a65 100644 --- a/src/main/java/org/thoughtcrime/securesms/connect/DcContactsLoader.java +++ b/src/main/java/org/thoughtcrime/securesms/connect/DcContactsLoader.java @@ -46,7 +46,7 @@ public class DcContactsLoader extends AsyncLoader { if (query == null && addScanQRLink) { additional_items = Util.appendInt(additional_items, DcContact.DC_CONTACT_ID_QR_INVITE); } - if (addCreateContactLink && !dcContext.isChatmail()) { + if (addCreateContactLink && dcContext.getConfigInt(DcHelper.CONFIG_FORCE_ENCRYPTION) == 0) { additional_items = Util.appendInt(additional_items, DcContact.DC_CONTACT_ID_NEW_CLASSIC_CONTACT); } @@ -54,7 +54,7 @@ public class DcContactsLoader extends AsyncLoader { additional_items = Util.appendInt(additional_items, DcContact.DC_CONTACT_ID_NEW_GROUP); additional_items = Util.appendInt(additional_items, DcContact.DC_CONTACT_ID_NEW_BROADCAST); - if (!dcContext.isChatmail()) { + if (dcContext.getConfigInt(DcHelper.CONFIG_FORCE_ENCRYPTION) == 0) { additional_items = Util.appendInt(additional_items, DcContact.DC_CONTACT_ID_NEW_UNENCRYPTED_GROUP); } diff --git a/src/main/java/org/thoughtcrime/securesms/qr/QrActivity.java b/src/main/java/org/thoughtcrime/securesms/qr/QrActivity.java index 350c3bfe8..1e8c7e42b 100644 --- a/src/main/java/org/thoughtcrime/securesms/qr/QrActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/qr/QrActivity.java @@ -18,6 +18,7 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.viewpager2.adapter.FragmentStateAdapter; import androidx.viewpager2.widget.ViewPager2; +import com.b44t.messenger.DcContext; import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayoutMediator; import com.google.zxing.BinaryBitmap; @@ -120,10 +121,12 @@ public class QrActivity extends BaseActionBarActivity implements View.OnClickLis @Override public boolean onPrepareOptionsMenu(Menu menu) { + DcContext dcContext = DcHelper.getContext(this); + menu.clear(); getMenuInflater().inflate(R.menu.qr_show, menu); menu.findItem(R.id.new_classic_contact) - .setVisible(!scanRelay && !DcHelper.getContext(this).isChatmail()); + .setVisible(!scanRelay && dcContext.getConfigInt(DcHelper.CONFIG_FORCE_ENCRYPTION) == 0); Util.redMenuItem(menu, R.id.withdraw); if (tabLayout.getSelectedTabPosition() == TAB_SCAN) { From 310a7f99c81d93aa9a0080a0e6ae738191c0de3b Mon Sep 17 00:00:00 2001 From: wchen342 Date: Thu, 11 Jun 2026 13:25:17 -0400 Subject: [PATCH 12/12] Remove media notification when audio playback ends naturally (#4472) --- CHANGELOG.md | 3 ++- .../components/audioplay/AudioPlaybackViewModel.java | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9099d2b77..9256e0639 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ * Fix: Incorrect total time when attaching audio files as draft * Fix: Audio files in draft showing total time from wrong file * Fix: Update the channel title after joining if the QR code had an outdated title -* Voice recording will be automatically saved as draft when interrupted +* Voice recording will be automatically saved as draft when interrupted +* Remove notification after audio playback ends * Update to core 2.52.0 ## v2.51.0 diff --git a/src/main/java/org/thoughtcrime/securesms/components/audioplay/AudioPlaybackViewModel.java b/src/main/java/org/thoughtcrime/securesms/components/audioplay/AudioPlaybackViewModel.java index 5bcc46013..1309b33d2 100644 --- a/src/main/java/org/thoughtcrime/securesms/components/audioplay/AudioPlaybackViewModel.java +++ b/src/main/java/org/thoughtcrime/securesms/components/audioplay/AudioPlaybackViewModel.java @@ -264,7 +264,10 @@ public class AudioPlaybackViewModel extends ViewModel { updateCurrentState(false); } else if (player.getPlaybackState() == Player.STATE_ENDED && !player.hasNextMediaItem()) { - mediaController.setPlayWhenReady(false); + mediaController.stop(); + mediaController.clearMediaItems(); + stopUpdateProgress(); + playbackState.setValue(AudioPlaybackState.idle()); } } }