From d851bf2a15818deffa942dbadc1cbc68fd80c6c1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 9 Sep 2025 08:59:57 +0200 Subject: [PATCH 1/3] Introduce createShortcutId to enforce the same computation of shortcut ids. Fixes https://github.com/element-hq/element-x-android/pull/5192#discussion_r2326482795 --- .../DefaultNotificationConversationService.kt | 5 +++-- .../notifications/factories/NotificationCreator.kt | 3 ++- .../push/impl/notifications/shortcut/Utils.kt | 13 +++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/shortcut/Utils.kt diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationService.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationService.kt index 2f535c9691..3796539f1a 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationService.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationService.kt @@ -33,6 +33,7 @@ import io.element.android.libraries.matrix.ui.media.InitialsAvatarBitmapGenerato import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader import io.element.android.libraries.push.api.notifications.conversations.NotificationConversationService import io.element.android.libraries.push.impl.intent.IntentProvider +import io.element.android.libraries.push.impl.notifications.shortcut.createShortcutId import io.element.android.libraries.sessionstorage.api.observer.SessionListener import io.element.android.libraries.sessionstorage.api.observer.SessionObserver import io.element.android.libraries.ui.strings.CommonStrings @@ -106,7 +107,7 @@ class DefaultNotificationConversationService( .generateBitmap(defaultShortcutIconSize, AvatarData(id = roomId.value, name = roomName, size = AvatarSize.RoomHeader)) ?.let(IconCompat::createWithAdaptiveBitmap) - val shortcutInfo = ShortcutInfoCompat.Builder(context, "$sessionId-$roomId") + val shortcutInfo = ShortcutInfoCompat.Builder(context, createShortcutId(sessionId, roomId)) .setShortLabel(roomName) .setIcon(icon) .setIntent(intentProvider.getViewRoomIntent(sessionId, roomId, threadId = null)) @@ -127,7 +128,7 @@ class DefaultNotificationConversationService( } override suspend fun onLeftRoom(sessionId: SessionId, roomId: RoomId) { - val shortcutsToRemove = listOf("$sessionId-$roomId") + val shortcutsToRemove = listOf(createShortcutId(sessionId, roomId)) runCatchingExceptions { ShortcutManagerCompat.removeDynamicShortcuts(context, shortcutsToRemove) if (isRequestPinShortcutSupported) { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt index 894c71f176..3c6a1adbbb 100755 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt @@ -41,6 +41,7 @@ import io.element.android.libraries.push.impl.notifications.model.FallbackNotifi import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent +import io.element.android.libraries.push.impl.notifications.shortcut.createShortcutId import io.element.android.services.toolbox.api.strings.StringProvider interface NotificationCreator { @@ -140,7 +141,7 @@ class DefaultNotificationCreator( // Must match those created in the ShortcutInfoCompat.Builder() // for the notification to appear as a "Conversation": // https://developer.android.com/develop/ui/views/notifications/conversations - .setShortcutId("${roomInfo.sessionId.value}-${roomInfo.roomId.value}") + .setShortcutId(createShortcutId(roomInfo.sessionId, roomInfo.roomId)) // Auto-bundling is enabled for 4 or more notifications on API 24+ (N+) // devices and all Wear devices. But we want a custom grouping, so we specify the groupID .setGroup(roomInfo.sessionId.value) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/shortcut/Utils.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/shortcut/Utils.kt new file mode 100644 index 0000000000..e2a8b4a742 --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/shortcut/Utils.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.notifications.shortcut + +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId + +internal fun createShortcutId(sessionId: SessionId, roomId: RoomId) = "$sessionId-$roomId" From bd6c0970165dd10ed5c1309340436623afc0352c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 9 Sep 2025 09:02:25 +0200 Subject: [PATCH 2/3] Introduce filterBySession() extension --- .../conversations/DefaultNotificationConversationService.kt | 3 ++- .../libraries/push/impl/notifications/shortcut/Utils.kt | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationService.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationService.kt index 3796539f1a..24b69a2e58 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationService.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationService.kt @@ -34,6 +34,7 @@ import io.element.android.libraries.push.api.notifications.NotificationBitmapLoa import io.element.android.libraries.push.api.notifications.conversations.NotificationConversationService import io.element.android.libraries.push.impl.intent.IntentProvider import io.element.android.libraries.push.impl.notifications.shortcut.createShortcutId +import io.element.android.libraries.push.impl.notifications.shortcut.filterBySession import io.element.android.libraries.sessionstorage.api.observer.SessionListener import io.element.android.libraries.sessionstorage.api.observer.SessionObserver import io.element.android.libraries.ui.strings.CommonStrings @@ -182,7 +183,7 @@ class DefaultNotificationConversationService( private fun onSessionLogOut(sessionId: SessionId) { runCatchingExceptions { val shortcuts = ShortcutManagerCompat.getDynamicShortcuts(context) - val shortcutIdsToRemove = shortcuts.filter { it.id.startsWith(sessionId.value) }.map { it.id } + val shortcutIdsToRemove = shortcuts.filterBySession(sessionId).map { it.id } ShortcutManagerCompat.removeDynamicShortcuts(context, shortcutIdsToRemove) if (isRequestPinShortcutSupported) { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/shortcut/Utils.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/shortcut/Utils.kt index e2a8b4a742..6176886dbc 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/shortcut/Utils.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/shortcut/Utils.kt @@ -7,7 +7,13 @@ package io.element.android.libraries.push.impl.notifications.shortcut +import androidx.core.content.pm.ShortcutInfoCompat import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId internal fun createShortcutId(sessionId: SessionId, roomId: RoomId) = "$sessionId-$roomId" + +internal fun Iterable.filterBySession(sessionId: SessionId): Iterable { + val prefix = "$sessionId-" + return filter { it.id.startsWith(prefix) } +} From 36b1b1348bd7872c14cc9981a98cf051dc27cab2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 9 Sep 2025 09:05:39 +0200 Subject: [PATCH 3/3] Use createShortcutId() in the test. --- .../DefaultNotificationConversationServiceTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationServiceTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationServiceTest.kt index 92adfd7812..d05966f9a6 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationServiceTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationServiceTest.kt @@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_SESSION_ID_2 import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.push.impl.notifications.factories.FakeIntentProvider +import io.element.android.libraries.push.impl.notifications.shortcut.createShortcutId import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader import io.element.android.libraries.sessionstorage.test.observer.FakeSessionObserver @@ -58,7 +59,7 @@ class DefaultNotificationConversationServiceTest { val context = InstrumentationRegistry.getInstrumentation().context val service = createService(context) - val shortcutId = "$A_SESSION_ID-$A_ROOM_ID" + val shortcutId = createShortcutId(A_SESSION_ID, A_ROOM_ID) val shortcutInfo = ShortcutInfoCompat.Builder(context, shortcutId) .setShortLabel("Room title") .setIntent(Intent(Intent.ACTION_VIEW))