From 7e7aca4a53b8d016f3f4e8139d799abb41740924 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 4 Apr 2023 17:33:22 +0200 Subject: [PATCH] Add some tests (mainly imported from EA). Also change type from String to SessionId, RoomId, etc. --- .../android/x/intent/IntentProviderImpl.kt | 5 +- .../libraries/core/cache/CircularCacheTest.kt | 71 ++++++ .../libraries/matrix/api/core/EventId.kt | 2 + .../libraries/matrix/api/core/RoomId.kt | 2 + .../libraries/matrix/api/core/SessionId.kt | 2 + .../libraries/matrix/api/core/SpaceId.kt | 2 + .../libraries/matrix/api/core/ThreadId.kt | 2 + .../libraries/matrix/api/core/UserId.kt | 2 + .../api/notification/NotificationService.kt | 6 +- .../notification/RustNotificationService.kt | 9 +- .../android/libraries/matrix/test/TestData.kt | 2 + .../notification/FakeNotificationService.kt | 5 +- libraries/push/impl/build.gradle.kts | 4 +- .../libraries/push/impl/PushersManager.kt | 7 +- .../impl/clientsecret/PushClientSecret.kt | 8 +- .../impl/clientsecret/PushClientSecretImpl.kt | 7 +- .../clientsecret/PushClientSecretStore.kt | 10 +- .../PushClientSecretStoreDataStore.kt | 14 +- .../push/impl/firebase/PushDataFirebase.kt | 6 +- .../push/impl/intent/IntentProvider.kt | 9 +- .../notifications/NotifiableEventResolver.kt | 25 +- .../NotificationBroadcastReceiver.kt | 29 ++- .../NotificationDrawerManager.kt | 17 +- .../notifications/NotificationEventQueue.kt | 29 ++- .../impl/notifications/NotificationFactory.kt | 20 +- .../notifications/NotificationIdProvider.kt | 11 +- .../notifications/NotificationRenderer.kt | 12 +- .../impl/notifications/NotificationUtils.kt | 33 +-- .../impl/notifications/RoomEventGroupInfo.kt | 7 +- .../notifications/RoomGroupMessageCreator.kt | 8 +- .../SummaryGroupMessageCreator.kt | 5 +- .../model/InviteNotifiableEvent.kt | 12 +- .../notifications/model/NotifiableEvent.kt | 11 +- .../model/NotifiableMessageEvent.kt | 20 +- .../model/SimpleNotifiableEvent.kt | 12 +- .../libraries/push/impl/push/PushData.kt | 9 +- .../libraries/push/impl/push/PushHandler.kt | 2 +- .../pushgateway/PushGatewayNotifyRequest.kt | 5 +- .../impl/unifiedpush/PushDataUnifiedPush.kt | 6 +- .../InMemoryPushClientSecretStore.kt | 14 +- .../clientsecret/PushClientSecretImplTest.kt | 5 +- .../NotifiableEventProcessorTest.kt | 194 +++++++++++++++ .../NotificationEventQueueTest.kt | 231 ++++++++++++++++++ .../notifications/NotificationFactoryTest.kt | 194 +++++++++++++++ .../notifications/NotificationRendererTest.kt | 227 +++++++++++++++++ .../fake/FakeNotificationDisplayer.kt | 42 ++++ .../fake/FakeNotificationFactory.kt | 54 ++++ .../fake/FakeNotificationUtils.kt | 40 +++ .../fake/FakeOutdatedEventDetector.kt | 34 +++ .../fake/FakeRoomGroupMessageCreator.kt | 42 ++++ .../fake/FakeSummaryGroupMessageCreator.kt | 25 ++ .../fixtures/NotifiableEventFixture.kt | 96 ++++++++ services/appnavstate/test/build.gradle.kts | 30 +++ .../appnavstate/test/AppNavStateFixture.kt | 44 ++++ 54 files changed, 1569 insertions(+), 151 deletions(-) create mode 100644 libraries/core/src/test/kotlin/io/element/android/libraries/core/cache/CircularCacheTest.kt create mode 100644 libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessorTest.kt create mode 100644 libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueueTest.kt create mode 100644 libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt create mode 100644 libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt create mode 100644 libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt create mode 100644 libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationFactory.kt create mode 100644 libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationUtils.kt create mode 100644 libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeOutdatedEventDetector.kt create mode 100644 libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt create mode 100644 libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt create mode 100644 libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt create mode 100644 services/appnavstate/test/build.gradle.kts create mode 100644 services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/AppNavStateFixture.kt diff --git a/app/src/main/kotlin/io/element/android/x/intent/IntentProviderImpl.kt b/app/src/main/kotlin/io/element/android/x/intent/IntentProviderImpl.kt index 94c8572f20..b3c7aa98e0 100644 --- a/app/src/main/kotlin/io/element/android/x/intent/IntentProviderImpl.kt +++ b/app/src/main/kotlin/io/element/android/x/intent/IntentProviderImpl.kt @@ -21,6 +21,9 @@ import android.content.Intent import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.push.impl.intent.IntentProvider import io.element.android.x.MainActivity import javax.inject.Inject @@ -34,7 +37,7 @@ class IntentProviderImpl @Inject constructor( return Intent(context, MainActivity::class.java) } - override fun getIntent(sessionId: String, roomId: String?, threadId: String?): Intent { + override fun getIntent(sessionId: SessionId, roomId: RoomId?, threadId: ThreadId?): Intent { // TODO Handle deeplink or pass parameters return Intent(context, MainActivity::class.java) } diff --git a/libraries/core/src/test/kotlin/io/element/android/libraries/core/cache/CircularCacheTest.kt b/libraries/core/src/test/kotlin/io/element/android/libraries/core/cache/CircularCacheTest.kt new file mode 100644 index 0000000000..574eec9e3a --- /dev/null +++ b/libraries/core/src/test/kotlin/io/element/android/libraries/core/cache/CircularCacheTest.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.core.cache + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class CircularCacheTest { + @Test + fun `when putting more than cache size then cache is limited to cache size`() { + val (cache, internalData) = createIntCache(cacheSize = 3) + + cache.putInOrder(1, 1, 1, 1, 1, 1) + + assertThat(internalData).isEqualTo(arrayOf(1, 1, 1)) + } + + @Test + fun `when putting more than cache then acts as FIFO`() { + val (cache, internalData) = createIntCache(cacheSize = 3) + + cache.putInOrder(1, 2, 3, 4) + + assertThat(internalData).isEqualTo(arrayOf(4, 2, 3)) + } + + @Test + fun `given empty cache when checking if contains key then is false`() { + val (cache, _) = createIntCache(cacheSize = 3) + + val result = cache.contains(1) + + assertThat(result).isFalse() + } + + @Test + fun `given cached key when checking if contains key then is true`() { + val (cache, _) = createIntCache(cacheSize = 3) + + cache.put(1) + val result = cache.contains(1) + + assertThat(result).isTrue() + } + + private fun createIntCache(cacheSize: Int): Pair, Array> { + var internalData: Array? = null + val factory: (Int) -> Array = { + Array(it) { null }.also { array -> internalData = array } + } + return CircularCache(cacheSize, factory) to internalData!! + } + + private fun CircularCache.putInOrder(vararg values: Int) { + values.forEach { put(it) } + } +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/EventId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/EventId.kt index 5c94fa30e9..dc5e7ab16a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/EventId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/EventId.kt @@ -20,3 +20,5 @@ import java.io.Serializable @JvmInline value class EventId(val value: String) : Serializable + +fun String.asEventId() = EventId(this) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomId.kt index 6c556c8b62..df10038b05 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomId.kt @@ -20,3 +20,5 @@ import java.io.Serializable @JvmInline value class RoomId(val value: String) : Serializable + +fun String.asRoomId() = RoomId(this) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SessionId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SessionId.kt index 6009aa5c03..bea1f3c671 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SessionId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SessionId.kt @@ -17,3 +17,5 @@ package io.element.android.libraries.matrix.api.core typealias SessionId = UserId + +fun String.asSessionId() = SessionId(this) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SpaceId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SpaceId.kt index bb06d53018..849dd7d637 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SpaceId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SpaceId.kt @@ -25,3 +25,5 @@ value class SpaceId(val value: String) : Serializable * Value to use when no space is selected by the user. */ val MAIN_SPACE = SpaceId("!mainSpace") + +fun String.asSpaceId() = SpaceId(this) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/ThreadId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/ThreadId.kt index f1d6e13482..57fc187406 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/ThreadId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/ThreadId.kt @@ -20,3 +20,5 @@ import java.io.Serializable @JvmInline value class ThreadId(val value: String) : Serializable + +fun String.asThreadId() = ThreadId(this) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UserId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UserId.kt index 00fbc6e928..216faade45 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UserId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UserId.kt @@ -20,3 +20,5 @@ import java.io.Serializable @JvmInline value class UserId(val value: String) : Serializable + +fun String.asUserId() = UserId(this) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationService.kt index 9dec0821a3..972873ab38 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationService.kt @@ -16,6 +16,10 @@ package io.element.android.libraries.matrix.api.notification +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId + interface NotificationService { - suspend fun getNotification(userId: String, roomId: String, eventId: String): Result + suspend fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId): Result } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt index 1197021161..1368c17243 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt @@ -17,6 +17,9 @@ package io.element.android.libraries.matrix.impl.notification import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.notification.NotificationData import io.element.android.libraries.matrix.api.notification.NotificationService import kotlinx.coroutines.withContext @@ -28,15 +31,15 @@ class RustNotificationService( ) : NotificationService { private val notificationMapper: NotificationMapper = NotificationMapper() - override suspend fun getNotification(userId: String, roomId: String, eventId: String): Result { + override suspend fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId): Result { return withContext(dispatchers.io) { runCatching { org.matrix.rustcomponents.sdk.NotificationService( basePath = File(baseDirectory, "sessions").absolutePath, - userId = userId + userId = userId.value ).use { // TODO Not implemented yet, see https://github.com/matrix-org/matrix-rust-sdk/issues/1628 - it.getNotificationItem(roomId, eventId)?.let { notificationItem -> + it.getNotificationItem(roomId.value, eventId.value)?.let { notificationItem -> notificationMapper.map(notificationItem) } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt index a09c8bc8f9..fe9ab7e37f 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt @@ -31,8 +31,10 @@ val A_USER_ID = UserId("@alice:server.org") val A_SESSION_ID = SessionId(A_USER_ID.value) val A_SPACE_ID = SpaceId("!aSpaceId") val A_ROOM_ID = RoomId("!aRoomId") +val A_ROOM_ID_2 = RoomId("!aRoomId2") val A_THREAD_ID = ThreadId("\$aThreadId") val AN_EVENT_ID = EventId("\$anEventId") +val AN_EVENT_ID_2 = EventId("\$anEventId2") const val A_UNIQUE_ID = "aUniqueId" const val A_ROOM_NAME = "A room name" diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/FakeNotificationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/FakeNotificationService.kt index 879a9694a3..7cb92d35c5 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/FakeNotificationService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/FakeNotificationService.kt @@ -16,11 +16,14 @@ package io.element.android.libraries.matrix.test.notification +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.notification.NotificationData import io.element.android.libraries.matrix.api.notification.NotificationService class FakeNotificationService : NotificationService { - override suspend fun getNotification(userId: String, roomId: String, eventId: String): Result { + override suspend fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId): Result { return Result.success(null) } } diff --git a/libraries/push/impl/build.gradle.kts b/libraries/push/impl/build.gradle.kts index ca280be6ba..abee3728f2 100644 --- a/libraries/push/impl/build.gradle.kts +++ b/libraries/push/impl/build.gradle.kts @@ -54,7 +54,6 @@ dependencies { exclude(group = "com.android.support", module = "support-annotations") } - implementation(platform(libs.google.firebase.bom)) implementation("com.google.firebase:firebase-messaging-ktx") @@ -62,7 +61,10 @@ dependencies { api("com.github.UnifiedPush:android-connector:2.1.1") testImplementation(libs.test.junit) + testImplementation(libs.test.mockk) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(libs.coroutines.test) + testImplementation(projects.libraries.matrix.test) + testImplementation(projects.services.appnavstate.test) } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt index 3cabf2386c..f8f445a6df 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.push.impl import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData import io.element.android.libraries.push.impl.clientsecret.PushClientSecret @@ -98,7 +99,7 @@ class PushersManager @Inject constructor( } else { // Register the pusher to the server matrixClient.pushersService().setHttpPusher( - createHttpPusher(pushKey, gateway, matrixClient.sessionId.value) + createHttpPusher(pushKey, gateway, matrixClient.sessionId) ).fold( { userDataStore.setCurrentRegisteredPushKey(pushKey) @@ -113,7 +114,7 @@ class PushersManager @Inject constructor( private suspend fun createHttpPusher( pushKey: String, gateway: String, - userId: String, + userId: SessionId, ): SetHttpPusherData = SetHttpPusherData( pushKey = pushKey, @@ -167,6 +168,6 @@ class PushersManager @Inject constructor( } companion object { - const val TEST_EVENT_ID = "\$THIS_IS_A_FAKE_EVENT_ID" + val TEST_EVENT_ID = EventId("\$THIS_IS_A_FAKE_EVENT_ID") } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecret.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecret.kt index 0db59e42f7..93f5f43ce4 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecret.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecret.kt @@ -16,20 +16,22 @@ package io.element.android.libraries.push.impl.clientsecret +import io.element.android.libraries.matrix.api.core.SessionId + interface PushClientSecret { /** * To call when registering a pusher. It will return the existing secret or create a new one. */ - suspend fun getSecretForUser(userId: String): String + suspend fun getSecretForUser(userId: SessionId): String /** * To call when receiving a push containing a client secret. * Return null if not found. */ - suspend fun getUserIdFromSecret(clientSecret: String): String? + suspend fun getUserIdFromSecret(clientSecret: String): SessionId? /** * To call when the user signs out. */ - suspend fun resetSecretForUser(userId: String) + suspend fun resetSecretForUser(userId: SessionId) } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecretImpl.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecretImpl.kt index 96f4ee25fd..b57b24d25e 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecretImpl.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecretImpl.kt @@ -18,6 +18,7 @@ package io.element.android.libraries.push.impl.clientsecret import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.core.SessionId import javax.inject.Inject @ContributesBinding(AppScope::class) @@ -25,7 +26,7 @@ class PushClientSecretImpl @Inject constructor( private val pushClientSecretFactory: PushClientSecretFactory, private val pushClientSecretStore: PushClientSecretStore, ) : PushClientSecret { - override suspend fun getSecretForUser(userId: String): String { + override suspend fun getSecretForUser(userId: SessionId): String { val existingSecret = pushClientSecretStore.getSecret(userId) if (existingSecret != null) { return existingSecret @@ -35,11 +36,11 @@ class PushClientSecretImpl @Inject constructor( return newSecret } - override suspend fun getUserIdFromSecret(clientSecret: String): String? { + override suspend fun getUserIdFromSecret(clientSecret: String): SessionId? { return pushClientSecretStore.getUserIdFromSecret(clientSecret) } - override suspend fun resetSecretForUser(userId: String) { + override suspend fun resetSecretForUser(userId: SessionId) { pushClientSecretStore.resetSecret(userId) } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecretStore.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecretStore.kt index f283ab4607..c5f7358241 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecretStore.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecretStore.kt @@ -16,9 +16,11 @@ package io.element.android.libraries.push.impl.clientsecret +import io.element.android.libraries.matrix.api.core.SessionId + interface PushClientSecretStore { - suspend fun storeSecret(userId: String, clientSecret: String) - suspend fun getSecret(userId: String): String? - suspend fun resetSecret(userId: String) - suspend fun getUserIdFromSecret(clientSecret: String): String? + suspend fun storeSecret(userId: SessionId, clientSecret: String) + suspend fun getSecret(userId: SessionId): String? + suspend fun resetSecret(userId: SessionId) + suspend fun getUserIdFromSecret(clientSecret: String): SessionId? } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecretStoreDataStore.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecretStoreDataStore.kt index b3befee36b..98b64d4e0b 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecretStoreDataStore.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecretStoreDataStore.kt @@ -25,6 +25,8 @@ import androidx.datastore.preferences.preferencesDataStore import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.asSessionId import kotlinx.coroutines.flow.first import javax.inject.Inject @@ -34,29 +36,29 @@ private val Context.dataStore: DataStore by preferencesDataStore(na class PushClientSecretStoreDataStore @Inject constructor( @ApplicationContext private val context: Context, ) : PushClientSecretStore { - override suspend fun storeSecret(userId: String, clientSecret: String) { + override suspend fun storeSecret(userId: SessionId, clientSecret: String) { context.dataStore.edit { settings -> settings[getPreferenceKeyForUser(userId)] = clientSecret } } - override suspend fun getSecret(userId: String): String? { + override suspend fun getSecret(userId: SessionId): String? { return context.dataStore.data.first()[getPreferenceKeyForUser(userId)] } - override suspend fun resetSecret(userId: String) { + override suspend fun resetSecret(userId: SessionId) { context.dataStore.edit { settings -> settings.remove(getPreferenceKeyForUser(userId)) } } - override suspend fun getUserIdFromSecret(clientSecret: String): String? { + override suspend fun getUserIdFromSecret(clientSecret: String): SessionId? { val keyValues = context.dataStore.data.first().asMap() val matchingKey = keyValues.keys.firstOrNull { keyValues[it] == clientSecret } - return matchingKey?.name + return matchingKey?.name?.asSessionId() } - private fun getPreferenceKeyForUser(userId: String) = stringPreferencesKey(userId) + private fun getPreferenceKeyForUser(userId: SessionId) = stringPreferencesKey(userId.value) } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/firebase/PushDataFirebase.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/firebase/PushDataFirebase.kt index af82ebce74..bcf48bab15 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/firebase/PushDataFirebase.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/firebase/PushDataFirebase.kt @@ -17,6 +17,8 @@ package io.element.android.libraries.push.impl.firebase import io.element.android.libraries.matrix.api.core.MatrixPatterns +import io.element.android.libraries.matrix.api.core.asEventId +import io.element.android.libraries.matrix.api.core.asRoomId import io.element.android.libraries.push.impl.push.PushData /** @@ -40,8 +42,8 @@ data class PushDataFirebase( ) fun PushDataFirebase.toPushData() = PushData( - eventId = eventId?.takeIf { MatrixPatterns.isEventId(it) }, - roomId = roomId?.takeIf { MatrixPatterns.isRoomId(it) }, + eventId = eventId?.takeIf { MatrixPatterns.isEventId(it) }?.asEventId(), + roomId = roomId?.takeIf { MatrixPatterns.isRoomId(it) }?.asRoomId(), unread = unread, clientSecret = clientSecret, ) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/intent/IntentProvider.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/intent/IntentProvider.kt index 0036da43f6..52abb3f6a4 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/intent/IntentProvider.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/intent/IntentProvider.kt @@ -17,6 +17,9 @@ package io.element.android.libraries.push.impl.intent import android.content.Intent +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.ThreadId interface IntentProvider { /** @@ -25,8 +28,8 @@ interface IntentProvider { fun getMainIntent(): Intent fun getIntent( - sessionId: String, - roomId: String?, - threadId: String?, + sessionId: SessionId, + roomId: RoomId?, + threadId: ThreadId?, ): Intent } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt index 487147eacb..6463773e7b 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt @@ -19,6 +19,7 @@ import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.notification.NotificationData @@ -52,13 +53,13 @@ class NotifiableEventResolver @Inject constructor( private val buildMeta: BuildMeta, ) { - suspend fun resolveEvent(userId: String, roomId: String, eventId: String): NotifiableEvent? { + suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? { // Restore session - val session = matrixAuthenticationService.restoreSession(SessionId(userId)).getOrNull() ?: return null + val session = matrixAuthenticationService.restoreSession(sessionId).getOrNull() ?: return null // TODO EAx, no need for a session? val notificationData = session.let {// TODO Use make the app crashes it.notificationService().getNotification( - userId = userId, + userId = sessionId, roomId = roomId, eventId = eventId, ) @@ -72,11 +73,11 @@ class NotifiableEventResolver @Inject constructor( } ).orDefault(roomId, eventId) - return notificationData.asNotifiableEvent(userId, roomId, eventId) + return notificationData.asNotifiableEvent(sessionId, roomId, eventId) } } -private fun NotificationData.asNotifiableEvent(userId: String, roomId: String, eventId: String): NotifiableEvent { +private fun NotificationData.asNotifiableEvent(userId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent { return NotifiableMessageEvent( sessionId = userId, roomId = roomId, @@ -105,12 +106,12 @@ private fun NotificationData.asNotifiableEvent(userId: String, roomId: String, e /** * TODO This is a temporary method for EAx */ -private fun NotificationData?.orDefault(roomId: String, eventId: String): NotificationData { +private fun NotificationData?.orDefault(roomId: RoomId, eventId: EventId): NotificationData { return this ?: NotificationData( item = MatrixTimelineItem.Event( event = EventTimelineItem( - uniqueIdentifier = eventId, - eventId = EventId(eventId), + uniqueIdentifier = eventId.value, + eventId = eventId, isEditable = false, isLocal = false, isOwn = false, @@ -121,18 +122,18 @@ private fun NotificationData?.orDefault(roomId: String, eventId: String): Notifi senderProfile = ProfileTimelineDetails.Unavailable, timestamp = System.currentTimeMillis(), content = MessageContent( - body = eventId, + body = eventId.value, inReplyTo = null, isEdited = false, type = TextMessageType( - body = eventId, + body = eventId.value, formatted = null ) ) ), ), - title = roomId, - subtitle = eventId, + title = roomId.value, + subtitle = eventId.value, isNoisy = false, avatarUrl = null, ) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiver.kt index 1139beccbd..e912b58f03 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiver.kt @@ -22,6 +22,11 @@ import android.content.Intent import androidx.core.app.RemoteInput import io.element.android.libraries.architecture.bindings import io.element.android.libraries.core.log.logger.LoggerTag +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.asRoomId +import io.element.android.libraries.matrix.api.core.asSessionId +import io.element.android.libraries.matrix.api.core.asThreadId import io.element.android.libraries.push.impl.log.notificationLoggerTag import io.element.android.services.analytics.api.AnalyticsTracker import io.element.android.services.toolbox.api.systemclock.SystemClock @@ -46,29 +51,29 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { if (intent == null || context == null) return context.bindings().inject(this) Timber.tag(loggerTag.value).v("NotificationBroadcastReceiver received : $intent") - val sessionId = intent.extras?.getString(KEY_SESSION_ID) ?: return + val sessionId = intent.extras?.getString(KEY_SESSION_ID)?.asSessionId() ?: return when (intent.action) { actionIds.smartReply -> handleSmartReply(intent, context) actionIds.dismissRoom -> - intent.getStringExtra(KEY_ROOM_ID)?.let { roomId -> + intent.getStringExtra(KEY_ROOM_ID)?.asRoomId()?.let { roomId -> notificationDrawerManager.updateEvents { it.clearMessagesForRoom(sessionId, roomId) } } actionIds.dismissSummary -> notificationDrawerManager.clearAllEvents(sessionId) actionIds.markRoomRead -> - intent.getStringExtra(KEY_ROOM_ID)?.let { roomId -> + intent.getStringExtra(KEY_ROOM_ID)?.asRoomId()?.let { roomId -> notificationDrawerManager.updateEvents { it.clearMessagesForRoom(sessionId, roomId) } handleMarkAsRead(sessionId, roomId) } actionIds.join -> { - intent.getStringExtra(KEY_ROOM_ID)?.let { roomId -> + intent.getStringExtra(KEY_ROOM_ID)?.asRoomId()?.let { roomId -> notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(sessionId, roomId) } handleJoinRoom(sessionId, roomId) } } actionIds.reject -> { - intent.getStringExtra(KEY_ROOM_ID)?.let { roomId -> + intent.getStringExtra(KEY_ROOM_ID)?.asRoomId()?.let { roomId -> notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(sessionId, roomId) } handleRejectRoom(sessionId, roomId) } @@ -76,7 +81,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { } } - private fun handleJoinRoom(sessionId: String, roomId: String) { + private fun handleJoinRoom(sessionId: SessionId, roomId: RoomId) { /* activeSessionHolder.getSafeActiveSession()?.let { session -> val room = session.getRoom(roomId) @@ -93,7 +98,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { */ } - private fun handleRejectRoom(sessionId: String, roomId: String) { + private fun handleRejectRoom(sessionId: SessionId, roomId: RoomId) { /* activeSessionHolder.getSafeActiveSession()?.let { session -> session.coroutineScope.launch { @@ -104,7 +109,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { */ } - private fun handleMarkAsRead(sessionId: String, roomId: String) { + private fun handleMarkAsRead(sessionId: SessionId, roomId: RoomId) { /* activeSessionHolder.getActiveSession().let { session -> val room = session.getRoom(roomId) @@ -120,11 +125,11 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { private fun handleSmartReply(intent: Intent, context: Context) { val message = getReplyMessage(intent) - val sessionId = intent.getStringExtra(KEY_SESSION_ID) - val roomId = intent.getStringExtra(KEY_ROOM_ID) - val threadId = intent.getStringExtra(KEY_THREAD_ID) + val sessionId = intent.getStringExtra(KEY_SESSION_ID)?.asSessionId() + val roomId = intent.getStringExtra(KEY_ROOM_ID)?.asRoomId() + val threadId = intent.getStringExtra(KEY_THREAD_ID)?.asThreadId() - if (message.isNullOrBlank() || roomId.isNullOrBlank()) { + if (message.isNullOrBlank() || roomId == null) { // ignore this event // Can this happen? should we update notification? return diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDrawerManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDrawerManager.kt index 35a3dd526a..313706f054 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDrawerManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDrawerManager.kt @@ -25,6 +25,9 @@ import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.push.api.store.PushDataStore import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent @@ -86,13 +89,13 @@ class NotificationDrawerManager @Inject constructor( is AppNavigationState.Space -> {} is AppNavigationState.Room -> { // Cleanup notification for current room - onEnteringRoom(appNavigationState.parentSpace.parentSession.sessionId.value, appNavigationState.roomId.value) + onEnteringRoom(appNavigationState.parentSpace.parentSession.sessionId, appNavigationState.roomId) } is AppNavigationState.Thread -> { onEnteringThread( - appNavigationState.parentRoom.parentSpace.parentSession.sessionId.value, - appNavigationState.parentRoom.roomId.value, - appNavigationState.threadId.value + appNavigationState.parentRoom.parentSpace.parentSession.sessionId, + appNavigationState.parentRoom.roomId, + appNavigationState.threadId ) } } @@ -136,7 +139,7 @@ class NotificationDrawerManager @Inject constructor( /** * Clear all known events and refresh the notification drawer. */ - fun clearAllEvents(sessionId: String) { + fun clearAllEvents(sessionId: SessionId) { updateEvents { it.clearMessagesForSession(sessionId) } } @@ -144,7 +147,7 @@ class NotificationDrawerManager @Inject constructor( * Should be called when the application is currently opened and showing timeline for the given roomId. * Used to ignore events related to that room (no need to display notification) and clean any existing notification on this room. */ - private fun onEnteringRoom(sessionId: String, roomId: String) { + private fun onEnteringRoom(sessionId: SessionId, roomId: RoomId) { updateEvents { it.clearMessagesForRoom(sessionId, roomId) } @@ -154,7 +157,7 @@ class NotificationDrawerManager @Inject constructor( * Should be called when the application is currently opened and showing timeline for the given threadId. * Used to ignore events related to that thread (no need to display notification) and clean any existing notification on this room. */ - private fun onEnteringThread(sessionId: String, roomId: String, threadId: String) { + private fun onEnteringThread(sessionId: SessionId, roomId: RoomId, threadId: ThreadId) { updateEvents { it.clearMessagesForThread(sessionId, roomId, threadId) } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueue.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueue.kt index 09f451c501..60fb1baa05 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueue.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueue.kt @@ -17,6 +17,10 @@ package io.element.android.libraries.push.impl.notifications import io.element.android.libraries.core.cache.CircularCache +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent @@ -30,10 +34,10 @@ data class NotificationEventQueue constructor( * Acts as a notification debouncer to stop already dismissed push notifications from * displaying again when the /sync response is delayed. */ - private val seenEventIds: CircularCache + private val seenEventIds: CircularCache ) { - fun markRedacted(eventIds: List) { + fun markRedacted(eventIds: List) { eventIds.forEach { redactedId -> queue.replace(redactedId) { when (it) { @@ -45,7 +49,8 @@ data class NotificationEventQueue constructor( } } - fun syncRoomEvents(roomsLeft: Collection, roomsJoined: Collection) { + // TODO EAx call this + fun syncRoomEvents(roomsLeft: Collection, roomsJoined: Collection) { if (roomsLeft.isNotEmpty() || roomsJoined.isNotEmpty()) { queue.removeAll { when (it) { @@ -125,30 +130,30 @@ data class NotificationEventQueue constructor( ) } - fun clearMemberShipNotificationForRoom(sessionId: String, roomId: String) { + fun clearMemberShipNotificationForRoom(sessionId: SessionId, roomId: RoomId) { Timber.d("clearMemberShipOfRoom $sessionId, $roomId") - queue.removeAll { it is InviteNotifiableEvent && it.sessionId == sessionId && it.roomId == roomId } + queue.removeAll { it is InviteNotifiableEvent && it.sessionId == sessionId && it.roomId == roomId } } - fun clearMessagesForSession(sessionId: String) { + fun clearMessagesForSession(sessionId: SessionId) { Timber.d("clearMessagesForSession $sessionId") - queue.removeAll { it is NotifiableMessageEvent && it.sessionId == sessionId} + queue.removeAll { it is NotifiableMessageEvent && it.sessionId == sessionId } } - fun clearMessagesForRoom(sessionId: String, roomId: String) { + fun clearMessagesForRoom(sessionId: SessionId, roomId: RoomId) { Timber.d("clearMessageEventOfRoom $sessionId, $roomId") - queue.removeAll { it is NotifiableMessageEvent && it.sessionId == sessionId && it.roomId == roomId } + queue.removeAll { it is NotifiableMessageEvent && it.sessionId == sessionId && it.roomId == roomId } } - fun clearMessagesForThread(sessionId: String, roomId: String, threadId: String) { + fun clearMessagesForThread(sessionId: SessionId, roomId: RoomId, threadId: ThreadId) { Timber.d("clearMessageEventOfThread $sessionId, $roomId, $threadId") - queue.removeAll { it is NotifiableMessageEvent && it.sessionId == sessionId && it.roomId == roomId && it.threadId == threadId } + queue.removeAll { it is NotifiableMessageEvent && it.sessionId == sessionId && it.roomId == roomId && it.threadId == threadId } } fun rawEvents(): List = queue } -private fun MutableList.replace(eventId: String, block: (NotifiableEvent) -> NotifiableEvent) { +private fun MutableList.replace(eventId: EventId, block: (NotifiableEvent) -> NotifiableEvent) { val indexToReplace = indexOfFirst { it.eventId == eventId } if (indexToReplace == -1) { return diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt index 1ffdc210d8..1a2d4a852f 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt @@ -17,6 +17,8 @@ package io.element.android.libraries.push.impl.notifications import android.app.Notification +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId 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 @@ -30,8 +32,8 @@ class NotificationFactory @Inject constructor( private val summaryGroupMessageCreator: SummaryGroupMessageCreator ) { - fun Map.toNotifications( - sessionId: String, + fun Map.toNotifications( + sessionId: SessionId, myUserDisplayName: String, myUserAvatarUrl: String? ): List { @@ -62,11 +64,11 @@ class NotificationFactory @Inject constructor( fun List>.toNotifications(): List { return map { (processed, event) -> when (processed) { - ProcessedEvent.Type.REMOVE -> OneShotNotification.Removed(key = event.roomId) + ProcessedEvent.Type.REMOVE -> OneShotNotification.Removed(key = event.roomId.value) ProcessedEvent.Type.KEEP -> OneShotNotification.Append( notificationUtils.buildRoomInvitationNotification(event), OneShotNotification.Append.Meta( - key = event.roomId, + key = event.roomId.value, summaryLine = event.description, isNoisy = event.noisy, timestamp = event.timestamp @@ -80,11 +82,11 @@ class NotificationFactory @Inject constructor( fun List>.toNotifications(): List { return map { (processed, event) -> when (processed) { - ProcessedEvent.Type.REMOVE -> OneShotNotification.Removed(key = event.eventId) + ProcessedEvent.Type.REMOVE -> OneShotNotification.Removed(key = event.eventId.value) ProcessedEvent.Type.KEEP -> OneShotNotification.Append( notificationUtils.buildSimpleEventNotification(event), OneShotNotification.Append.Meta( - key = event.eventId, + key = event.eventId.value, summaryLine = event.description, isNoisy = event.noisy, timestamp = event.timestamp @@ -95,7 +97,7 @@ class NotificationFactory @Inject constructor( } fun createSummaryNotification( - sessionId: String, + sessionId: SessionId, roomNotifications: List, invitationNotifications: List, simpleNotifications: List, @@ -120,10 +122,10 @@ class NotificationFactory @Inject constructor( } sealed interface RoomNotification { - data class Removed(val roomId: String) : RoomNotification + data class Removed(val roomId: RoomId) : RoomNotification data class Message(val notification: Notification, val meta: Meta) : RoomNotification { data class Meta( - val roomId: String, + val roomId: RoomId, val summaryLine: CharSequence, val messageCount: Int, val latestTimestamp: Long, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationIdProvider.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationIdProvider.kt index 3a715eb844..04fc43221b 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationIdProvider.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationIdProvider.kt @@ -18,27 +18,28 @@ package io.element.android.libraries.push.impl.notifications import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.matrix.api.core.SessionId import javax.inject.Inject @SingleIn(AppScope::class) class NotificationIdProvider @Inject constructor() { - fun getSummaryNotificationId(sessionId: String): Int { + fun getSummaryNotificationId(sessionId: SessionId): Int { return getOffset(sessionId) + SUMMARY_NOTIFICATION_ID } - fun getRoomMessagesNotificationId(sessionId: String): Int { + fun getRoomMessagesNotificationId(sessionId: SessionId): Int { return getOffset(sessionId) + ROOM_MESSAGES_NOTIFICATION_ID } - fun getRoomEventNotificationId(sessionId: String): Int { + fun getRoomEventNotificationId(sessionId: SessionId): Int { return getOffset(sessionId) + ROOM_EVENT_NOTIFICATION_ID } - fun getRoomInvitationNotificationId(sessionId: String): Int { + fun getRoomInvitationNotificationId(sessionId: SessionId): Int { return getOffset(sessionId) + ROOM_INVITATION_NOTIFICATION_ID } - private fun getOffset(sessionId: String): Int { + private fun getOffset(sessionId: SessionId): Int { // TODO EAx multi account: return different value for users and persist data return 0 } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt index aa30cb04db..521f4f3c8c 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt @@ -16,6 +16,8 @@ package io.element.android.libraries.push.impl.notifications import androidx.annotation.WorkerThread +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent @@ -31,7 +33,7 @@ class NotificationRenderer @Inject constructor( @WorkerThread fun render( - sessionId: String, + sessionId: SessionId, myUserDisplayName: String, myUserAvatarUrl: String?, useCompleteNotificationFormat: Boolean, @@ -60,12 +62,12 @@ class NotificationRenderer @Inject constructor( when (wrapper) { is RoomNotification.Removed -> { Timber.d("Removing room messages notification ${wrapper.roomId}") - notificationDisplayer.cancelNotificationMessage(wrapper.roomId, notificationIdProvider.getRoomMessagesNotificationId(sessionId)) + notificationDisplayer.cancelNotificationMessage(wrapper.roomId.value, notificationIdProvider.getRoomMessagesNotificationId(sessionId)) } is RoomNotification.Message -> if (useCompleteNotificationFormat) { Timber.d("Updating room messages notification ${wrapper.meta.roomId}") notificationDisplayer.showNotificationMessage( - wrapper.meta.roomId, + wrapper.meta.roomId.value, notificationIdProvider.getRoomMessagesNotificationId(sessionId), wrapper.notification ) @@ -125,7 +127,7 @@ class NotificationRenderer @Inject constructor( } private fun List>.groupByType(): GroupedNotificationEvents { - val roomIdToEventMap: MutableMap>> = LinkedHashMap() + val roomIdToEventMap: MutableMap>> = LinkedHashMap() val simpleEvents: MutableList> = ArrayList() val invitationEvents: MutableList> = ArrayList() forEach { @@ -145,7 +147,7 @@ private fun List>.groupByType(): GroupedNotifica private fun ProcessedEvent.castedToEventType(): ProcessedEvent = this as ProcessedEvent data class GroupedNotificationEvents( - val roomEvents: Map>>, + val roomEvents: Map>>, val simpleEvents: List>, val invitationEvents: List> ) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationUtils.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationUtils.kt index 65fb475db8..2802b5489c 100755 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationUtils.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationUtils.kt @@ -44,6 +44,9 @@ import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.intent.IntentProvider import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent @@ -215,7 +218,7 @@ class NotificationUtils @Inject constructor( fun buildMessagesListNotification( messageStyle: NotificationCompat.MessagingStyle, roomInfo: RoomEventGroupInfo, - threadId: String?, + threadId: ThreadId?, largeIcon: Bitmap?, lastMessageTimestamp: Long, senderDisplayNameForReplyCompat: String?, @@ -244,7 +247,7 @@ class NotificationUtils @Inject constructor( // that can be displayed in not disturb mode if white listed (the later will need compat28.x) .setCategory(NotificationCompat.CATEGORY_MESSAGE) // ID of the corresponding shortcut, for conversation features under API 30+ - .setShortcutId(roomInfo.roomId) + .setShortcutId(roomInfo.roomId.value) // Title for API < 16 devices. .setContentTitle(roomInfo.roomDisplayName) // Content for API < 16 devices. @@ -259,7 +262,7 @@ class NotificationUtils @Inject constructor( ) // 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) + .setGroup(roomInfo.sessionId.value) // In order to avoid notification making sound twice (due to the summary notification) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) .setSmallIcon(smallIcon) @@ -359,7 +362,7 @@ class NotificationUtils @Inject constructor( .setOnlyAlertOnce(true) .setContentTitle(inviteNotifiableEvent.roomName ?: buildMeta.applicationName) .setContentText(inviteNotifiableEvent.description) - .setGroup(inviteNotifiableEvent.sessionId) + .setGroup(inviteNotifiableEvent.sessionId.value) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) .setSmallIcon(smallIcon) .setColor(accentColor) @@ -446,7 +449,7 @@ class NotificationUtils @Inject constructor( .setOnlyAlertOnce(true) .setContentTitle(buildMeta.applicationName) .setContentText(simpleNotifiableEvent.description) - .setGroup(simpleNotifiableEvent.sessionId) + .setGroup(simpleNotifiableEvent.sessionId.value) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) .setSmallIcon(smallIcon) .setColor(accentColor) @@ -477,7 +480,7 @@ class NotificationUtils @Inject constructor( .build() } - private fun buildOpenRoomIntent(sessionId: String, roomId: String): PendingIntent? { + private fun buildOpenRoomIntent(sessionId: SessionId, roomId: RoomId): PendingIntent? { val roomIntent = intentProvider.getIntent(sessionId = sessionId, roomId = roomId, threadId = null) roomIntent.action = actionIds.tapToView // pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that @@ -491,7 +494,7 @@ class NotificationUtils @Inject constructor( ) } - private fun buildOpenThreadIntent(roomInfo: RoomEventGroupInfo, threadId: String?): PendingIntent? { + private fun buildOpenThreadIntent(roomInfo: RoomEventGroupInfo, threadId: ThreadId?): PendingIntent? { val sessionId = roomInfo.sessionId val roomId = roomInfo.roomId val threadIntentTap = intentProvider.getIntent(sessionId = sessionId, roomId = roomId, threadId = threadId) @@ -507,7 +510,7 @@ class NotificationUtils @Inject constructor( ) } - private fun buildOpenHomePendingIntentForSummary(sessionId: String): PendingIntent { + private fun buildOpenHomePendingIntentForSummary(sessionId: SessionId): PendingIntent { val intent = intentProvider.getIntent(sessionId = sessionId, roomId = null, threadId = null) intent.data = createIgnoredUri("tapSummary?$sessionId") return PendingIntent.getActivity( @@ -526,9 +529,9 @@ class NotificationUtils @Inject constructor( it will be more appropriate to use an activity. Since you have to provide your own UI. */ private fun buildQuickReplyIntent( - sessionId: String, - roomId: String, - threadId: String?, + sessionId: SessionId, + roomId: RoomId, + threadId: ThreadId?, senderName: String? ): PendingIntent? { val intent: Intent @@ -573,7 +576,7 @@ class NotificationUtils @Inject constructor( * Build the summary notification. */ fun buildSummaryListNotification( - sessionId: String, + sessionId: SessionId, style: NotificationCompat.InboxStyle?, compatSummary: String, noisy: Boolean, @@ -587,12 +590,12 @@ class NotificationUtils @Inject constructor( // used in compat < N, after summary is built based on child notifications .setWhen(lastMessageTimestamp) .setStyle(style) - .setContentTitle(sessionId) + .setContentTitle(sessionId.value) .setCategory(NotificationCompat.CATEGORY_MESSAGE) .setSmallIcon(smallIcon) // set content text to support devices running API level < 24 .setContentText(compatSummary) - .setGroup(sessionId) + .setGroup(sessionId.value) // set this notification as the summary for the group .setGroupSummary(true) .setColor(accentColor) @@ -616,7 +619,7 @@ class NotificationUtils @Inject constructor( .build() } - private fun getDismissSummaryPendingIntent(sessionId: String): PendingIntent { + private fun getDismissSummaryPendingIntent(sessionId: SessionId): PendingIntent { val intent = Intent(context, NotificationBroadcastReceiver::class.java) intent.action = actionIds.dismissSummary intent.data = createIgnoredUri("deleteSummary?$sessionId") diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomEventGroupInfo.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomEventGroupInfo.kt index 668a4bb38a..b2a5fcbedb 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomEventGroupInfo.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomEventGroupInfo.kt @@ -16,12 +16,15 @@ package io.element.android.libraries.push.impl.notifications +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId + /** * Data class to hold information about a group of notifications for a room. */ data class RoomEventGroupInfo( - val sessionId: String, - val roomId: String, + val sessionId: SessionId, + val roomId: RoomId, val roomDisplayName: String, val isDirect: Boolean = false ) { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt index 362cd86f4c..ab38ad9fb4 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt @@ -19,6 +19,8 @@ package io.element.android.libraries.push.impl.notifications import android.graphics.Bitmap import androidx.core.app.NotificationCompat import androidx.core.app.Person +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent import io.element.android.services.toolbox.api.strings.StringProvider @@ -34,9 +36,9 @@ class RoomGroupMessageCreator @Inject constructor( ) { fun createRoomMessage( - sessionId: String, + sessionId: SessionId, events: List, - roomId: String, + roomId: RoomId, userDisplayName: String, userAvatarUrl: String? ): RoomNotification.Message { @@ -47,7 +49,7 @@ class RoomGroupMessageCreator @Inject constructor( Person.Builder() .setName(userDisplayName) .setIcon(bitmapLoader.getUserIcon(userAvatarUrl)) - .setKey(lastKnownRoomEvent.sessionId) + .setKey(lastKnownRoomEvent.sessionId.value) .build() ).also { it.conversationTitle = roomName.takeIf { roomIsGroup } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt index 2826f01101..07471fd9f8 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt @@ -18,6 +18,7 @@ package io.element.android.libraries.push.impl.notifications import android.app.Notification import androidx.core.app.NotificationCompat +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.push.impl.R import io.element.android.services.toolbox.api.strings.StringProvider import javax.inject.Inject @@ -42,7 +43,7 @@ class SummaryGroupMessageCreator @Inject constructor( ) { fun createSummaryNotification( - sessionId: String, + sessionId: SessionId, roomNotifications: List, invitationNotifications: List, simpleNotifications: List, @@ -92,7 +93,7 @@ class SummaryGroupMessageCreator @Inject constructor( } private fun processSimpleGroupSummary( - sessionId: String, + sessionId: SessionId, summaryIsNoisy: Boolean, messageEventsCount: Int, simpleEventsCount: Int, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/InviteNotifiableEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/InviteNotifiableEvent.kt index 5bb8124f16..4524d27ac2 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/InviteNotifiableEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/InviteNotifiableEvent.kt @@ -15,11 +15,15 @@ */ package io.element.android.libraries.push.impl.notifications.model +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId + data class InviteNotifiableEvent( - override val sessionId: String, - override val roomId: String, - override val eventId: String, - override val editedEventId: String?, + override val sessionId: SessionId, + override val roomId: RoomId, + override val eventId: EventId, + override val editedEventId: EventId?, override val canBeReplaced: Boolean, val roomName: String?, val noisy: Boolean, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableEvent.kt index 7324cd7042..40d84496a5 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableEvent.kt @@ -15,16 +15,19 @@ */ package io.element.android.libraries.push.impl.notifications.model +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId import java.io.Serializable /** * Parent interface for all events which can be displayed as a Notification. */ sealed interface NotifiableEvent : Serializable { - val sessionId: String - val roomId: String - val eventId: String - val editedEventId: String? + val sessionId: SessionId + val roomId: RoomId + val eventId: EventId + val editedEventId: EventId? // Used to know if event should be replaced with the one coming from eventstream val canBeReplaced: Boolean diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt index 657c60d3b0..add172e44f 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt @@ -16,16 +16,20 @@ package io.element.android.libraries.push.impl.notifications.model import android.net.Uri +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.services.appnavstate.api.AppNavigationState import io.element.android.services.appnavstate.api.currentRoomId import io.element.android.services.appnavstate.api.currentSessionId import io.element.android.services.appnavstate.api.currentThreadId data class NotifiableMessageEvent( - override val sessionId: String, - override val roomId: String, - override val eventId: String, - override val editedEventId: String?, + override val sessionId: SessionId, + override val roomId: RoomId, + override val eventId: EventId, + override val editedEventId: EventId?, override val canBeReplaced: Boolean, val noisy: Boolean, val timestamp: Long, @@ -35,7 +39,7 @@ data class NotifiableMessageEvent( // We cannot use Uri? type here, as that could trigger a // NotSerializableException when persisting this to storage val imageUriString: String?, - val threadId: String?, + val threadId: ThreadId?, val roomName: String?, val roomIsDirect: Boolean = false, val roomAvatarPath: String? = null, @@ -59,9 +63,9 @@ data class NotifiableMessageEvent( fun NotifiableMessageEvent.shouldIgnoreMessageEventInRoom( appNavigationState: AppNavigationState? ): Boolean { - val currentSessionId = appNavigationState?.currentSessionId()?.value ?: return false - return when (val currentRoomId = appNavigationState.currentRoomId()?.value) { + val currentSessionId = appNavigationState?.currentSessionId() ?: return false + return when (val currentRoomId = appNavigationState.currentRoomId()) { null -> false - else -> sessionId == currentSessionId && roomId == currentRoomId && threadId == appNavigationState.currentThreadId()?.value + else -> sessionId == currentSessionId && roomId == currentRoomId && threadId == appNavigationState.currentThreadId() } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/SimpleNotifiableEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/SimpleNotifiableEvent.kt index 5ebaea7a58..5cfd04474a 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/SimpleNotifiableEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/SimpleNotifiableEvent.kt @@ -15,11 +15,15 @@ */ package io.element.android.libraries.push.impl.notifications.model +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId + data class SimpleNotifiableEvent( - override val sessionId: String, - override val roomId: String, - override val eventId: String, - override val editedEventId: String?, + override val sessionId: SessionId, + override val roomId: RoomId, + override val eventId: EventId, + override val editedEventId: EventId?, val noisy: Boolean, val title: String, val description: String, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/PushData.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/PushData.kt index 0955c864cd..31d3eb7ea0 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/PushData.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/PushData.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright (c) 2023 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,9 @@ package io.element.android.libraries.push.impl.push +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId + /** * Represent parsed data that the app has received from a Push content. * @@ -24,8 +27,8 @@ package io.element.android.libraries.push.impl.push * @property unread Number of unread message. */ data class PushData( - val eventId: String?, - val roomId: String?, + val eventId: EventId?, + val roomId: RoomId?, val unread: Int?, val clientSecret: String?, ) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/PushHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/PushHandler.kt index 4250bef867..1c597c010c 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/PushHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/PushHandler.kt @@ -119,7 +119,7 @@ class PushHandler @Inject constructor( // Get userId from client secret pushClientSecret.getUserIdFromSecret(clientSecret) } ?: run { - matrixAuthenticationService.getLatestSessionId()?.value + matrixAuthenticationService.getLatestSessionId() } if (userId == null) { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyRequest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyRequest.kt index 37e97a238c..7130e38d6e 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyRequest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyRequest.kt @@ -15,6 +15,7 @@ */ package io.element.android.libraries.push.impl.pushgateway +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.network.RetrofitFactory import io.element.android.libraries.push.api.gateway.PushGatewayFailure import javax.inject.Inject @@ -26,7 +27,7 @@ class PushGatewayNotifyRequest @Inject constructor( val url: String, val appId: String, val pushKey: String, - val eventId: String + val eventId: EventId ) suspend fun execute(params: Params) { @@ -38,7 +39,7 @@ class PushGatewayNotifyRequest @Inject constructor( val response = sygnalApi.notify( PushGatewayNotifyBody( PushGatewayNotification( - eventId = params.eventId, + eventId = params.eventId.value, devices = listOf( PushGatewayDevice( params.appId, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/unifiedpush/PushDataUnifiedPush.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/unifiedpush/PushDataUnifiedPush.kt index 3e6a8199ec..56513ab970 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/unifiedpush/PushDataUnifiedPush.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/unifiedpush/PushDataUnifiedPush.kt @@ -17,6 +17,8 @@ package io.element.android.libraries.push.impl.unifiedpush import io.element.android.libraries.matrix.api.core.MatrixPatterns +import io.element.android.libraries.matrix.api.core.asEventId +import io.element.android.libraries.matrix.api.core.asRoomId import io.element.android.libraries.push.impl.push.PushData import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -55,8 +57,8 @@ data class PushDataUnifiedPushCounts( ) fun PushDataUnifiedPush.toPushData() = PushData( - eventId = notification?.eventId?.takeIf { MatrixPatterns.isEventId(it) }, - roomId = notification?.roomId?.takeIf { MatrixPatterns.isRoomId(it) }, + eventId = notification?.eventId?.takeIf { MatrixPatterns.isEventId(it) }?.asEventId(), + roomId = notification?.roomId?.takeIf { MatrixPatterns.isRoomId(it) }?.asRoomId(), unread = notification?.counts?.unread, clientSecret = null // TODO EAx check how client secret will be sent through UnifiedPush ) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/clientsecret/InMemoryPushClientSecretStore.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/clientsecret/InMemoryPushClientSecretStore.kt index 0bc826398a..a2d2d9c83c 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/clientsecret/InMemoryPushClientSecretStore.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/clientsecret/InMemoryPushClientSecretStore.kt @@ -16,24 +16,26 @@ package io.element.android.libraries.push.impl.clientsecret +import io.element.android.libraries.matrix.api.core.SessionId + class InMemoryPushClientSecretStore : PushClientSecretStore { - private val secrets = mutableMapOf() + private val secrets = mutableMapOf() - fun getSecrets(): Map = secrets + fun getSecrets(): Map = secrets - override suspend fun storeSecret(userId: String, clientSecret: String) { + override suspend fun storeSecret(userId: SessionId, clientSecret: String) { secrets[userId] = clientSecret } - override suspend fun getSecret(userId: String): String? { + override suspend fun getSecret(userId: SessionId): String? { return secrets[userId] } - override suspend fun resetSecret(userId: String) { + override suspend fun resetSecret(userId: SessionId) { secrets.remove(userId) } - override suspend fun getUserIdFromSecret(clientSecret: String): String? { + override suspend fun getUserIdFromSecret(clientSecret: String): SessionId? { return secrets.keys.firstOrNull { secrets[it] == clientSecret } } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecretImplTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecretImplTest.kt index 1a6d52e660..a9d740bf31 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecretImplTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/clientsecret/PushClientSecretImplTest.kt @@ -19,12 +19,13 @@ package io.element.android.libraries.push.impl.clientsecret import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.core.SessionId import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test -private const val A_USER_ID_0 = "A_USER_ID_0" -private const val A_USER_ID_1 = "A_USER_ID_1" +private val A_USER_ID_0 = SessionId("A_USER_ID_0") +private val A_USER_ID_1 = SessionId("A_USER_ID_1") private const val A_UNKNOWN_SECRET = "A_UNKNOWN_SECRET" diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessorTest.kt new file mode 100644 index 0000000000..80246abb14 --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessorTest.kt @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.notifications + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_ROOM_ID_2 +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.A_SPACE_ID +import io.element.android.libraries.matrix.test.A_THREAD_ID +import io.element.android.libraries.push.impl.notifications.fake.FakeOutdatedEventDetector +import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent +import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent +import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent +import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent +import io.element.android.services.appnavstate.test.anAppNavigationState +import org.junit.Test + +private val NOT_VIEWING_A_ROOM = anAppNavigationState() +private val VIEWING_A_ROOM = anAppNavigationState(A_SESSION_ID, A_SPACE_ID, A_ROOM_ID) +private val VIEWING_A_THREAD = anAppNavigationState(A_SESSION_ID, A_SPACE_ID, A_ROOM_ID, A_THREAD_ID) + +class NotifiableEventProcessorTest { + + private val outdatedDetector = FakeOutdatedEventDetector() + private val eventProcessor = NotifiableEventProcessor(outdatedDetector.instance) + + @Test + fun `given simple events when processing then keep simple events`() { + val events = listOf( + aSimpleNotifiableEvent(eventId = AN_EVENT_ID), + aSimpleNotifiableEvent(eventId = AN_EVENT_ID_2) + ) + + val result = eventProcessor.process(events, appNavigationState = NOT_VIEWING_A_ROOM, renderedEvents = emptyList()) + + assertThat(result).isEqualTo( + listOfProcessedEvents( + ProcessedEvent.Type.KEEP to events[0], + ProcessedEvent.Type.KEEP to events[1] + ) + ) + } + + @Test + fun `given redacted simple event when processing then remove redaction event`() { + val events = listOf(aSimpleNotifiableEvent(eventId = AN_EVENT_ID, type = "m.room.redaction")) + + val result = eventProcessor.process(events, appNavigationState = NOT_VIEWING_A_ROOM, renderedEvents = emptyList()) + + assertThat(result).isEqualTo( + listOfProcessedEvents( + ProcessedEvent.Type.REMOVE to events[0] + ) + ) + } + + @Test + fun `given invites are not auto accepted when processing then keep invitation events`() { + val events = listOf( + anInviteNotifiableEvent(roomId = A_ROOM_ID), + anInviteNotifiableEvent(roomId = A_ROOM_ID_2) + ) + + val result = eventProcessor.process(events, appNavigationState = NOT_VIEWING_A_ROOM, renderedEvents = emptyList()) + + assertThat(result).isEqualTo( + listOfProcessedEvents( + ProcessedEvent.Type.KEEP to events[0], + ProcessedEvent.Type.KEEP to events[1] + ) + ) + } + + @Test + fun `given out of date message event when processing then removes message event`() { + val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID)) + outdatedDetector.givenEventIsOutOfDate(events[0]) + + val result = eventProcessor.process(events, appNavigationState = NOT_VIEWING_A_ROOM, renderedEvents = emptyList()) + + assertThat(result).isEqualTo( + listOfProcessedEvents( + ProcessedEvent.Type.REMOVE to events[0], + ) + ) + } + + @Test + fun `given in date message event when processing then keep message event`() { + val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID)) + outdatedDetector.givenEventIsInDate(events[0]) + + val result = eventProcessor.process(events, appNavigationState = NOT_VIEWING_A_ROOM, renderedEvents = emptyList()) + + assertThat(result).isEqualTo( + listOfProcessedEvents( + ProcessedEvent.Type.KEEP to events[0], + ) + ) + } + + @Test + fun `given viewing the same room main timeline when processing main timeline message event then removes message`() { + val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID, threadId = null)) + + val result = eventProcessor.process(events, VIEWING_A_ROOM, renderedEvents = emptyList()) + + assertThat(result).isEqualTo( + listOfProcessedEvents( + ProcessedEvent.Type.REMOVE to events[0], + ) + ) + } + + @Test + fun `given viewing the same thread timeline when processing thread message event then removes message`() { + val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID, threadId = A_THREAD_ID)) + + val result = eventProcessor.process(events, VIEWING_A_THREAD, renderedEvents = emptyList()) + + assertThat(result).isEqualTo( + listOfProcessedEvents( + ProcessedEvent.Type.REMOVE to events[0], + ) + ) + } + + @Test + fun `given viewing main timeline of the same room when processing thread timeline message event then keep message`() { + val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID, threadId = A_THREAD_ID)) + outdatedDetector.givenEventIsInDate(events[0]) + + val result = eventProcessor.process(events, VIEWING_A_ROOM, renderedEvents = emptyList()) + + assertThat(result).isEqualTo( + listOfProcessedEvents( + ProcessedEvent.Type.KEEP to events[0], + ) + ) + } + + @Test + fun `given viewing thread timeline of the same room when processing main timeline message event then keep message`() { + val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID)) + outdatedDetector.givenEventIsInDate(events[0]) + + val result = eventProcessor.process(events, VIEWING_A_THREAD, renderedEvents = emptyList()) + + assertThat(result).isEqualTo( + listOfProcessedEvents( + ProcessedEvent.Type.KEEP to events[0], + ) + ) + } + + @Test + fun `given events are different to rendered events when processing then removes difference`() { + val events = listOf(aSimpleNotifiableEvent(eventId = AN_EVENT_ID)) + val renderedEvents = listOf>( + ProcessedEvent(ProcessedEvent.Type.KEEP, events[0]), + ProcessedEvent(ProcessedEvent.Type.KEEP, anInviteNotifiableEvent(eventId = AN_EVENT_ID_2)) + ) + + val result = eventProcessor.process(events, appNavigationState = NOT_VIEWING_A_ROOM, renderedEvents = renderedEvents) + + assertThat(result).isEqualTo( + listOfProcessedEvents( + ProcessedEvent.Type.REMOVE to renderedEvents[1].event, + ProcessedEvent.Type.KEEP to renderedEvents[0].event + ) + ) + } + + private fun listOfProcessedEvents(vararg event: Pair) = event.map { + ProcessedEvent(it.first, it.second) + } +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueueTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueueTest.kt new file mode 100644 index 0000000000..4bec096468 --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueueTest.kt @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.notifications + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.core.cache.CircularCache +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent +import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent +import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent +import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent +import org.junit.Test + +class NotificationEventQueueTest { + + private val seenIdsCache = CircularCache.create(5) + + @Test + fun `given events when redacting some then marks matching event ids as redacted`() { + val queue = givenQueue( + listOf( + aSimpleNotifiableEvent(eventId = EventId("redacted-id-1")), + aNotifiableMessageEvent(eventId = EventId("redacted-id-2")), + anInviteNotifiableEvent(eventId = EventId("redacted-id-3")), + aSimpleNotifiableEvent(eventId = EventId("kept-id")), + ) + ) + + queue.markRedacted(listOf(EventId("redacted-id-1"), EventId("redacted-id-2"), EventId("redacted-id-3"))) + + assertThat(queue.rawEvents()).isEqualTo( + listOf( + aSimpleNotifiableEvent(eventId = EventId("redacted-id-1"), isRedacted = true), + aNotifiableMessageEvent(eventId = EventId("redacted-id-2"), isRedacted = true), + anInviteNotifiableEvent(eventId = EventId("redacted-id-3"), isRedacted = true), + aSimpleNotifiableEvent(eventId = EventId("kept-id"), isRedacted = false), + ) + ) + } + + @Test + fun `given invite event when leaving invited room and syncing then removes event`() { + val queue = givenQueue(listOf(anInviteNotifiableEvent(roomId = A_ROOM_ID))) + val roomsLeft = listOf(A_ROOM_ID) + + queue.syncRoomEvents(roomsLeft = roomsLeft, roomsJoined = emptyList()) + + assertThat(queue.rawEvents()).isEmpty() + } + + @Test + fun `given invite event when joining invited room and syncing then removes event`() { + val queue = givenQueue(listOf(anInviteNotifiableEvent(roomId = A_ROOM_ID))) + val joinedRooms = listOf(A_ROOM_ID) + + queue.syncRoomEvents(roomsLeft = emptyList(), roomsJoined = joinedRooms) + + assertThat(queue.rawEvents()).isEmpty() + } + + @Test + fun `given message event when leaving message room and syncing then removes event`() { + val queue = givenQueue(listOf(aNotifiableMessageEvent(roomId = A_ROOM_ID))) + val roomsLeft = listOf(A_ROOM_ID) + + queue.syncRoomEvents(roomsLeft = roomsLeft, roomsJoined = emptyList()) + + assertThat(queue.rawEvents()).isEmpty() + } + + @Test + fun `given events when syncing without rooms left or joined ids then does not change the events`() { + val queue = givenQueue( + listOf( + aNotifiableMessageEvent(roomId = A_ROOM_ID), + anInviteNotifiableEvent(roomId = A_ROOM_ID) + ) + ) + + queue.syncRoomEvents(roomsLeft = emptyList(), roomsJoined = emptyList()) + + assertThat(queue.rawEvents()).isEqualTo( + listOf( + aNotifiableMessageEvent(roomId = A_ROOM_ID), + anInviteNotifiableEvent(roomId = A_ROOM_ID) + ) + ) + } + + @Test + fun `given events then is not empty`() { + val queue = givenQueue(listOf(aSimpleNotifiableEvent())) + + assertThat(queue.isEmpty()).isFalse() + } + + @Test + fun `given no events then is empty`() { + val queue = givenQueue(emptyList()) + + assertThat(queue.isEmpty()).isTrue() + } + + @Test + fun `given events when clearing and adding then removes previous events and adds only new events`() { + val queue = givenQueue(listOf(aSimpleNotifiableEvent())) + + queue.clearAndAdd(listOf(anInviteNotifiableEvent())) + + assertThat(queue.rawEvents()).isEqualTo(listOf(anInviteNotifiableEvent())) + } + + @Test + fun `when clearing then is empty`() { + val queue = givenQueue(listOf(aSimpleNotifiableEvent())) + + queue.clear() + + assertThat(queue.rawEvents()).isEmpty() + } + + @Test + fun `given no events when adding then adds event`() { + val queue = givenQueue(listOf()) + + queue.add(aSimpleNotifiableEvent()) + + assertThat(queue.rawEvents()).isEqualTo(listOf(aSimpleNotifiableEvent())) + } + + @Test + fun `given no events when adding already seen event then ignores event`() { + val queue = givenQueue(listOf()) + val notifiableEvent = aSimpleNotifiableEvent() + seenIdsCache.put(notifiableEvent.eventId) + + queue.add(notifiableEvent) + + assertThat(queue.rawEvents()).isEmpty() + } + + @Test + fun `given replaceable event when adding event with same id then updates existing event`() { + val replaceableEvent = aSimpleNotifiableEvent(canBeReplaced = true) + val updatedEvent = replaceableEvent.copy(title = "updated title", isUpdated = true) + val queue = givenQueue(listOf(replaceableEvent)) + + queue.add(updatedEvent) + + assertThat(queue.rawEvents()).isEqualTo(listOf(updatedEvent)) + } + + @Test + fun `given non replaceable event when adding event with same id then ignores event`() { + val nonReplaceableEvent = aSimpleNotifiableEvent(canBeReplaced = false) + val updatedEvent = nonReplaceableEvent.copy(title = "updated title") + val queue = givenQueue(listOf(nonReplaceableEvent)) + + queue.add(updatedEvent) + + assertThat(queue.rawEvents()).isEqualTo(listOf(nonReplaceableEvent)) + } + + @Test + fun `given event when adding new event with edited event id matching the existing event id then updates existing event`() { + val editedEvent = aSimpleNotifiableEvent(eventId = EventId("id-to-edit")) + val updatedEvent = editedEvent.copy(eventId = EventId("1"), editedEventId = EventId("id-to-edit"), title = "updated title", isUpdated = true) + val queue = givenQueue(listOf(editedEvent)) + + queue.add(updatedEvent) + + assertThat(queue.rawEvents()).isEqualTo(listOf(updatedEvent)) + } + + @Test + fun `given event when adding new event with edited event id matching the existing event edited id then updates existing event`() { + val editedEvent = aSimpleNotifiableEvent(eventId = EventId("0"), editedEventId = EventId("id-to-edit")) + val updatedEvent = editedEvent.copy(eventId = EventId("1"), editedEventId = EventId("id-to-edit"), title = "updated title", isUpdated = true) + val queue = givenQueue(listOf(editedEvent)) + + queue.add(updatedEvent) + + assertThat(queue.rawEvents()).isEqualTo(listOf(updatedEvent)) + } + + @Test + fun `when clearing membership notification then removes invite events with matching room id`() { + val queue = givenQueue( + listOf( + anInviteNotifiableEvent(roomId = A_ROOM_ID), + aNotifiableMessageEvent(roomId = A_ROOM_ID) + ) + ) + + queue.clearMemberShipNotificationForRoom(A_SESSION_ID, A_ROOM_ID) + + assertThat(queue.rawEvents()).isEqualTo(listOf(aNotifiableMessageEvent(roomId = A_ROOM_ID))) + } + + @Test + fun `when clearing messages for room then removes message events with matching room id`() { + val queue = givenQueue( + listOf( + anInviteNotifiableEvent(roomId = A_ROOM_ID), + aNotifiableMessageEvent(roomId = A_ROOM_ID) + ) + ) + + queue.clearMessagesForRoom(A_SESSION_ID, A_ROOM_ID) + + assertThat(queue.rawEvents()).isEqualTo(listOf(anInviteNotifiableEvent(roomId = A_ROOM_ID))) + } + + private fun givenQueue(events: List) = NotificationEventQueue(events.toMutableList(), seenEventIds = seenIdsCache) +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt new file mode 100644 index 0000000000..606923eb1f --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.notifications + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationUtils +import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator +import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator +import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent +import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent +import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent +import org.junit.Test + +private val MY_AVATAR_URL: String? = null +private val AN_INVITATION_EVENT = anInviteNotifiableEvent(roomId = A_ROOM_ID) +private val A_SIMPLE_EVENT = aSimpleNotifiableEvent(eventId = AN_EVENT_ID) +private val A_MESSAGE_EVENT = aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID) + +class NotificationFactoryTest { + + private val notificationUtils = FakeNotificationUtils() + private val roomGroupMessageCreator = FakeRoomGroupMessageCreator() + private val summaryGroupMessageCreator = FakeSummaryGroupMessageCreator() + + private val notificationFactory = NotificationFactory( + notificationUtils.instance, + roomGroupMessageCreator.instance, + summaryGroupMessageCreator.instance + ) + + @Test + fun `given a room invitation when mapping to notification then is Append`() = testWith(notificationFactory) { + val expectedNotification = notificationUtils.givenBuildRoomInvitationNotificationFor(AN_INVITATION_EVENT) + val roomInvitation = listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, AN_INVITATION_EVENT)) + + val result = roomInvitation.toNotifications() + + assertThat(result).isEqualTo( + listOf( + OneShotNotification.Append( + notification = expectedNotification, + meta = OneShotNotification.Append.Meta( + key = A_ROOM_ID.value, + summaryLine = AN_INVITATION_EVENT.description, + isNoisy = AN_INVITATION_EVENT.noisy, + timestamp = AN_INVITATION_EVENT.timestamp + ) + ) + ) + ) + } + + @Test + fun `given a missing event in room invitation when mapping to notification then is Removed`() = testWith(notificationFactory) { + val missingEventRoomInvitation = listOf(ProcessedEvent(ProcessedEvent.Type.REMOVE, AN_INVITATION_EVENT)) + + val result = missingEventRoomInvitation.toNotifications() + + assertThat(result).isEqualTo( + listOf( + OneShotNotification.Removed( + key = A_ROOM_ID.value + ) + ) + ) + } + + @Test + fun `given a simple event when mapping to notification then is Append`() = testWith(notificationFactory) { + val expectedNotification = notificationUtils.givenBuildSimpleInvitationNotificationFor(A_SIMPLE_EVENT) + val roomInvitation = listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, A_SIMPLE_EVENT)) + + val result = roomInvitation.toNotifications() + + assertThat(result).isEqualTo( + listOf( + OneShotNotification.Append( + notification = expectedNotification, + meta = OneShotNotification.Append.Meta( + key = AN_EVENT_ID.value, + summaryLine = A_SIMPLE_EVENT.description, + isNoisy = A_SIMPLE_EVENT.noisy, + timestamp = AN_INVITATION_EVENT.timestamp + ) + ) + ) + ) + } + + @Test + fun `given a missing simple event when mapping to notification then is Removed`() = testWith(notificationFactory) { + val missingEventRoomInvitation = listOf(ProcessedEvent(ProcessedEvent.Type.REMOVE, A_SIMPLE_EVENT)) + + val result = missingEventRoomInvitation.toNotifications() + + assertThat(result).isEqualTo( + listOf( + OneShotNotification.Removed( + key = AN_EVENT_ID.value + ) + ) + ) + } + + @Test + fun `given room with message when mapping to notification then delegates to room group message creator`() = testWith(notificationFactory) { + val events = listOf(A_MESSAGE_EVENT) + val expectedNotification = roomGroupMessageCreator.givenCreatesRoomMessageFor( + A_SESSION_ID, events, A_ROOM_ID, A_SESSION_ID.value, MY_AVATAR_URL + ) + val roomWithMessage = mapOf(A_ROOM_ID to listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, A_MESSAGE_EVENT))) + + val result = roomWithMessage.toNotifications(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL) + + assertThat(result).isEqualTo(listOf(expectedNotification)) + } + + @Test + fun `given a room with no events to display when mapping to notification then is Empty`() = testWith(notificationFactory) { + val events = listOf(ProcessedEvent(ProcessedEvent.Type.REMOVE, A_MESSAGE_EVENT)) + val emptyRoom = mapOf(A_ROOM_ID to events) + + val result = emptyRoom.toNotifications(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL) + + assertThat(result).isEqualTo( + listOf( + RoomNotification.Removed( + roomId = A_ROOM_ID + ) + ) + ) + } + + @Test + fun `given a room with only redacted events when mapping to notification then is Empty`() = testWith(notificationFactory) { + val redactedRoom = mapOf(A_ROOM_ID to listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, A_MESSAGE_EVENT.copy(isRedacted = true)))) + + val result = redactedRoom.toNotifications(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL) + + assertThat(result).isEqualTo( + listOf( + RoomNotification.Removed( + roomId = A_ROOM_ID + ) + ) + ) + } + + @Test + fun `given a room with redacted and non redacted message events when mapping to notification then redacted events are removed`() = testWith( + notificationFactory + ) { + val roomWithRedactedMessage = mapOf( + A_ROOM_ID to listOf( + ProcessedEvent(ProcessedEvent.Type.KEEP, A_MESSAGE_EVENT.copy(isRedacted = true)), + ProcessedEvent(ProcessedEvent.Type.KEEP, A_MESSAGE_EVENT.copy(eventId = EventId("not-redacted"))) + ) + ) + val withRedactedRemoved = listOf(A_MESSAGE_EVENT.copy(eventId = EventId("not-redacted"))) + val expectedNotification = roomGroupMessageCreator.givenCreatesRoomMessageFor( + A_SESSION_ID, + withRedactedRemoved, + A_ROOM_ID, + A_SESSION_ID.value, + MY_AVATAR_URL + ) + + val result = roomWithRedactedMessage.toNotifications(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL) + + assertThat(result).isEqualTo(listOf(expectedNotification)) + } +} + +fun testWith(receiver: T, block: T.() -> Unit) { + receiver.block() +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt new file mode 100644 index 0000000000..79c6dfdb02 --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.notifications + +import android.app.Notification +import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer +import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationFactory +import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent +import io.mockk.mockk +import org.junit.Test + +private const val MY_USER_DISPLAY_NAME = "display-name" +private const val MY_USER_AVATAR_URL = "avatar-url" +private const val USE_COMPLETE_NOTIFICATION_FORMAT = true + +private val AN_EVENT_LIST = listOf>() +private val A_PROCESSED_EVENTS = GroupedNotificationEvents(emptyMap(), emptyList(), emptyList()) +private val A_SUMMARY_NOTIFICATION = SummaryNotification.Update(mockk()) +private val A_REMOVE_SUMMARY_NOTIFICATION = SummaryNotification.Removed +private val A_NOTIFICATION = mockk() +private val MESSAGE_META = RoomNotification.Message.Meta( + summaryLine = "ignored", messageCount = 1, latestTimestamp = -1, roomId = A_ROOM_ID, shouldBing = false +) +private val ONE_SHOT_META = OneShotNotification.Append.Meta(key = "ignored", summaryLine = "ignored", isNoisy = false, timestamp = -1) + +class NotificationRendererTest { + + private val notificationDisplayer = FakeNotificationDisplayer() + private val notificationFactory = FakeNotificationFactory() + private val notificationIdProvider = NotificationIdProvider() + + private val notificationRenderer = NotificationRenderer( + notificationIdProvider = notificationIdProvider, + notificationDisplayer = notificationDisplayer.instance, + notificationFactory = notificationFactory.instance, + ) + + @Test + fun `given no notifications when rendering then cancels summary notification`() { + givenNoNotifications() + + renderEventsAsNotifications() + + notificationDisplayer.verifySummaryCancelled() + notificationDisplayer.verifyNoOtherInteractions() + } + + @Test + fun `given last room message group notification is removed when rendering then remove the summary and then remove message notification`() { + givenNotifications(roomNotifications = listOf(RoomNotification.Removed(A_ROOM_ID)), summaryNotification = A_REMOVE_SUMMARY_NOTIFICATION) + + renderEventsAsNotifications() + + notificationDisplayer.verifyInOrder { + cancelNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID)) + cancelNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID)) + } + } + + @Test + fun `given a room message group notification is removed when rendering then remove the message notification and update summary`() { + givenNotifications(roomNotifications = listOf(RoomNotification.Removed(A_ROOM_ID))) + + renderEventsAsNotifications() + + notificationDisplayer.verifyInOrder { + cancelNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID)) + showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) + } + } + + @Test + fun `given a room message group notification is added when rendering then show the message notification and update summary`() { + givenNotifications( + roomNotifications = listOf( + RoomNotification.Message( + A_NOTIFICATION, + MESSAGE_META + ) + ) + ) + + renderEventsAsNotifications() + + notificationDisplayer.verifyInOrder { + showNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID), A_NOTIFICATION) + showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) + } + } + + @Test + fun `given last simple notification is removed when rendering then remove the summary and then remove simple notification`() { + givenNotifications(simpleNotifications = listOf(OneShotNotification.Removed(AN_EVENT_ID.value)), summaryNotification = A_REMOVE_SUMMARY_NOTIFICATION) + + renderEventsAsNotifications() + + notificationDisplayer.verifyInOrder { + cancelNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID)) + cancelNotificationMessage(tag = AN_EVENT_ID.value, notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID)) + } + } + + @Test + fun `given a simple notification is removed when rendering then remove the simple notification and update summary`() { + givenNotifications(simpleNotifications = listOf(OneShotNotification.Removed(AN_EVENT_ID.value))) + + renderEventsAsNotifications() + + notificationDisplayer.verifyInOrder { + cancelNotificationMessage(tag = AN_EVENT_ID.value, notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID)) + showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) + } + } + + @Test + fun `given a simple notification is added when rendering then show the simple notification and update summary`() { + givenNotifications( + simpleNotifications = listOf( + OneShotNotification.Append( + A_NOTIFICATION, + ONE_SHOT_META.copy(key = AN_EVENT_ID.value) + ) + ) + ) + + renderEventsAsNotifications() + + notificationDisplayer.verifyInOrder { + showNotificationMessage(tag = AN_EVENT_ID.value, notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID), A_NOTIFICATION) + showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) + } + } + + @Test + fun `given last invitation notification is removed when rendering then remove the summary and then remove invitation notification`() { + givenNotifications(invitationNotifications = listOf(OneShotNotification.Removed(A_ROOM_ID.value)), summaryNotification = A_REMOVE_SUMMARY_NOTIFICATION) + + renderEventsAsNotifications() + + notificationDisplayer.verifyInOrder { + cancelNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID)) + cancelNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomInvitationNotificationId(A_SESSION_ID)) + } + } + + @Test + fun `given an invitation notification is removed when rendering then remove the invitation notification and update summary`() { + givenNotifications(invitationNotifications = listOf(OneShotNotification.Removed(A_ROOM_ID.value))) + + renderEventsAsNotifications() + + notificationDisplayer.verifyInOrder { + cancelNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomInvitationNotificationId(A_SESSION_ID)) + showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) + } + } + + @Test + fun `given an invitation notification is added when rendering then show the invitation notification and update summary`() { + givenNotifications( + simpleNotifications = listOf( + OneShotNotification.Append( + A_NOTIFICATION, + ONE_SHOT_META.copy(key = A_ROOM_ID.value) + ) + ) + ) + + renderEventsAsNotifications() + + notificationDisplayer.verifyInOrder { + showNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID), A_NOTIFICATION) + showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) + } + } + + private fun renderEventsAsNotifications() { + notificationRenderer.render( + sessionId = A_SESSION_ID, + myUserDisplayName = MY_USER_DISPLAY_NAME, + myUserAvatarUrl = MY_USER_AVATAR_URL, + useCompleteNotificationFormat = USE_COMPLETE_NOTIFICATION_FORMAT, + eventsToProcess = AN_EVENT_LIST + ) + } + + private fun givenNoNotifications() { + givenNotifications(emptyList(), emptyList(), emptyList(), USE_COMPLETE_NOTIFICATION_FORMAT, A_REMOVE_SUMMARY_NOTIFICATION) + } + + private fun givenNotifications( + roomNotifications: List = emptyList(), + invitationNotifications: List = emptyList(), + simpleNotifications: List = emptyList(), + useCompleteNotificationFormat: Boolean = USE_COMPLETE_NOTIFICATION_FORMAT, + summaryNotification: SummaryNotification = A_SUMMARY_NOTIFICATION + ) { + notificationFactory.givenNotificationsFor( + groupedEvents = A_PROCESSED_EVENTS, + sessionId = A_SESSION_ID, + myUserDisplayName = MY_USER_DISPLAY_NAME, + myUserAvatarUrl = MY_USER_AVATAR_URL, + useCompleteNotificationFormat = useCompleteNotificationFormat, + roomNotifications = roomNotifications, + invitationNotifications = invitationNotifications, + simpleNotifications = simpleNotifications, + summaryNotification = summaryNotification + ) + } +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt new file mode 100644 index 0000000000..9af681490a --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.notifications.fake + +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.push.impl.notifications.NotificationDisplayer +import io.element.android.libraries.push.impl.notifications.NotificationIdProvider +import io.mockk.confirmVerified +import io.mockk.mockk +import io.mockk.verify +import io.mockk.verifyOrder + +class FakeNotificationDisplayer { + val instance = mockk(relaxed = true) + + fun verifySummaryCancelled() { + verify { instance.cancelNotificationMessage(tag = null, NotificationIdProvider().getSummaryNotificationId(A_SESSION_ID)) } + } + + fun verifyNoOtherInteractions() { + confirmVerified(instance) + } + + fun verifyInOrder(verifyBlock: NotificationDisplayer.() -> Unit) { + verifyOrder { verifyBlock(instance) } + verifyNoOtherInteractions() + } +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationFactory.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationFactory.kt new file mode 100644 index 0000000000..d9ba17c28e --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationFactory.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.notifications.fake + +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.push.impl.notifications.* +import io.mockk.every +import io.mockk.mockk + +class FakeNotificationFactory { + val instance = mockk() + + fun givenNotificationsFor( + groupedEvents: GroupedNotificationEvents, + sessionId: SessionId, + myUserDisplayName: String, + myUserAvatarUrl: String?, + useCompleteNotificationFormat: Boolean, + roomNotifications: List, + invitationNotifications: List, + simpleNotifications: List, + summaryNotification: SummaryNotification + ) { + with(instance) { + every { groupedEvents.roomEvents.toNotifications(sessionId, myUserDisplayName, myUserAvatarUrl) } returns roomNotifications + every { groupedEvents.invitationEvents.toNotifications() } returns invitationNotifications + every { groupedEvents.simpleEvents.toNotifications() } returns simpleNotifications + + every { + createSummaryNotification( + sessionId, + roomNotifications, + invitationNotifications, + simpleNotifications, + useCompleteNotificationFormat + ) + } returns summaryNotification + } + } +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationUtils.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationUtils.kt new file mode 100644 index 0000000000..046b2edd87 --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationUtils.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.notifications.fake + +import android.app.Notification +import io.element.android.libraries.push.impl.notifications.NotificationUtils +import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent +import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent +import io.mockk.every +import io.mockk.mockk + +class FakeNotificationUtils { + val instance = mockk() + + fun givenBuildRoomInvitationNotificationFor(event: InviteNotifiableEvent): Notification { + val mockNotification = mockk() + every { instance.buildRoomInvitationNotification(event) } returns mockNotification + return mockNotification + } + + fun givenBuildSimpleInvitationNotificationFor(event: SimpleNotifiableEvent): Notification { + val mockNotification = mockk() + every { instance.buildSimpleEventNotification(event) } returns mockNotification + return mockNotification + } +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeOutdatedEventDetector.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeOutdatedEventDetector.kt new file mode 100644 index 0000000000..03bf7e8491 --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeOutdatedEventDetector.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.notifications.fake + +import io.element.android.libraries.push.impl.notifications.OutdatedEventDetector +import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent +import io.mockk.every +import io.mockk.mockk + +class FakeOutdatedEventDetector { + val instance = mockk() + + fun givenEventIsOutOfDate(notifiableEvent: NotifiableEvent) { + every { instance.isMessageOutdated(notifiableEvent) } returns true + } + + fun givenEventIsInDate(notifiableEvent: NotifiableEvent) { + every { instance.isMessageOutdated(notifiableEvent) } returns false + } +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt new file mode 100644 index 0000000000..df0b5ad42b --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.notifications.fake + +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.push.impl.notifications.RoomGroupMessageCreator +import io.element.android.libraries.push.impl.notifications.RoomNotification +import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent +import io.mockk.every +import io.mockk.mockk + +class FakeRoomGroupMessageCreator { + + val instance = mockk() + + fun givenCreatesRoomMessageFor( + sessionId: SessionId, + events: List, + roomId: RoomId, + userDisplayName: String, + userAvatarUrl: String? + ): RoomNotification.Message { + val mockMessage = mockk() + every { instance.createRoomMessage(sessionId, events, roomId, userDisplayName, userAvatarUrl) } returns mockMessage + return mockMessage + } +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt new file mode 100644 index 0000000000..fc7b0553eb --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.notifications.fake + +import io.element.android.libraries.push.impl.notifications.SummaryGroupMessageCreator +import io.mockk.mockk + +class FakeSummaryGroupMessageCreator { + + val instance = mockk() +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt new file mode 100644 index 0000000000..9a998abf43 --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.notifications.fixtures + +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.ThreadId +import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_SESSION_ID +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 + +fun aSimpleNotifiableEvent( + sessionId: SessionId = A_SESSION_ID, + roomId: RoomId = A_ROOM_ID, + eventId: EventId = AN_EVENT_ID, + type: String? = null, + isRedacted: Boolean = false, + canBeReplaced: Boolean = false, + editedEventId: EventId? = null +) = SimpleNotifiableEvent( + sessionId = sessionId, + roomId = roomId, + eventId = eventId, + editedEventId = editedEventId, + noisy = false, + title = "title", + description = "description", + type = type, + timestamp = 0, + soundName = null, + canBeReplaced = canBeReplaced, + isRedacted = isRedacted +) + +fun anInviteNotifiableEvent( + sessionId: SessionId = A_SESSION_ID, + roomId: RoomId = A_ROOM_ID, + eventId: EventId = AN_EVENT_ID, + isRedacted: Boolean = false +) = InviteNotifiableEvent( + sessionId = sessionId, + eventId = eventId, + roomId = roomId, + roomName = "a room name", + editedEventId = null, + noisy = false, + title = "title", + description = "description", + type = null, + timestamp = 0, + soundName = null, + canBeReplaced = false, + isRedacted = isRedacted +) + +fun aNotifiableMessageEvent( + sessionId: SessionId = A_SESSION_ID, + roomId: RoomId = A_ROOM_ID, + eventId: EventId = AN_EVENT_ID, + threadId: ThreadId? = null, + isRedacted: Boolean = false +) = NotifiableMessageEvent( + sessionId = sessionId, + eventId = eventId, + editedEventId = null, + noisy = false, + timestamp = 0, + senderName = "sender-name", + senderId = "sending-id", + body = "message-body", + roomId = roomId, + threadId = threadId, + roomName = "room-name", + roomIsDirect = false, + canBeReplaced = false, + isRedacted = isRedacted, + imageUriString = null +) diff --git a/services/appnavstate/test/build.gradle.kts b/services/appnavstate/test/build.gradle.kts new file mode 100644 index 0000000000..cda023b236 --- /dev/null +++ b/services/appnavstate/test/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed +@Suppress("DSL_SCOPE_VIOLATION") +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.services.appnavstate.test" +} + +dependencies { + api(projects.libraries.matrix.api) + api(projects.services.appnavstate.api) +} diff --git a/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/AppNavStateFixture.kt b/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/AppNavStateFixture.kt new file mode 100644 index 0000000000..6afab893c3 --- /dev/null +++ b/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/AppNavStateFixture.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.services.appnavstate.test + +import io.element.android.libraries.matrix.api.core.* +import io.element.android.services.appnavstate.api.AppNavigationState + +fun anAppNavigationState( + sessionId: SessionId? = null, + spaceId: SpaceId? = MAIN_SPACE, + roomId: RoomId? = null, + threadId: ThreadId? = null, +): AppNavigationState { + if (sessionId == null) { + return AppNavigationState.Root + } + val session = AppNavigationState.Session(sessionId) + if (spaceId == null) { + return session + } + val space = AppNavigationState.Space(spaceId, session) + if (roomId == null) { + return space + } + val room = AppNavigationState.Room(roomId, space) + if (threadId == null) { + return room + } + return AppNavigationState.Thread(threadId, room) +}